├── .editorconfig ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ ├── fr.yml │ └── other.yml ├── RELEASE_TEMPLATE.md ├── renovate.json └── workflows │ ├── code_quality.yml │ ├── gradle-wrapper.yml │ ├── nightly-merge.yml │ ├── publish.yml │ └── tasks.yml ├── .gitignore ├── .idea ├── .gitignore ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── discord.xml ├── gradle.xml ├── icon.png ├── inspectionProfiles │ └── Project_Default.xml ├── jsonSchemas.xml ├── kotlinc.xml ├── migrations.xml └── misc.xml ├── Changelog.md ├── LICENSE ├── README.md ├── app ├── .gitignore ├── aapt2-resources.cfg ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── debug │ ├── AndroidManifest.xml │ └── res │ │ └── xml │ │ └── shortcuts.xml │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── qhy040404 │ │ │ └── libraryonetap │ │ │ ├── LibraryOneTapApp.kt │ │ │ ├── base │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseFragment.kt │ │ │ ├── BaseViewHolder.kt │ │ │ └── IBinding.kt │ │ │ ├── compat │ │ │ ├── PMCompat.kt │ │ │ └── WICompat.kt │ │ │ ├── constant │ │ │ ├── Constants.kt │ │ │ ├── GlobalManager.kt │ │ │ ├── GlobalValues.kt │ │ │ ├── OnceTag.kt │ │ │ ├── URLManager.kt │ │ │ └── enums │ │ │ │ ├── GPAAlgorithm.kt │ │ │ │ ├── InsetsParams.kt │ │ │ │ ├── OrderModes.kt │ │ │ │ └── Parentheses.kt │ │ │ ├── data │ │ │ ├── CancelDTO.kt │ │ │ ├── ElectricDTO.kt │ │ │ ├── GithubAPIDTO.kt │ │ │ ├── NetDTO.kt │ │ │ ├── OrderListData.kt │ │ │ ├── ReserveDTO.kt │ │ │ ├── SessionDTO.kt │ │ │ ├── VCardStatusDTO.kt │ │ │ ├── VolunteerDTO.kt │ │ │ └── tools │ │ │ │ ├── Exam.kt │ │ │ │ ├── Grade.kt │ │ │ │ ├── Lesson.kt │ │ │ │ └── Semester.kt │ │ │ ├── recyclerview │ │ │ ├── SimplePageActivity.kt │ │ │ └── simplepage │ │ │ │ ├── Card.kt │ │ │ │ ├── CardView.kt │ │ │ │ ├── CardViewBinder.kt │ │ │ │ ├── Category.kt │ │ │ │ ├── CategoryView.kt │ │ │ │ ├── CategoryViewBinder.kt │ │ │ │ ├── Clickable.kt │ │ │ │ ├── ClickableView.kt │ │ │ │ ├── ClickableViewBinder.kt │ │ │ │ └── DividerItemDecoration.kt │ │ │ ├── ui │ │ │ ├── about │ │ │ │ ├── AboutActivity.kt │ │ │ │ ├── AbsAboutActivityProxy.java │ │ │ │ ├── ClickableViewHolder.java │ │ │ │ ├── ContributorViewBinder.java │ │ │ │ ├── DividerItemDecoration.java │ │ │ │ └── LicenseViewBinder.java │ │ │ ├── dialog │ │ │ │ └── ReserveDialog.kt │ │ │ ├── fragment │ │ │ │ ├── fullscreen │ │ │ │ │ └── FullScreenDialogFragment.kt │ │ │ │ ├── library │ │ │ │ │ ├── SingleFragment.kt │ │ │ │ │ └── YanxiujianFragment.kt │ │ │ │ ├── settings │ │ │ │ │ └── SettingsFragment.kt │ │ │ │ └── tools │ │ │ │ │ └── ToolsInitFragment.kt │ │ │ ├── interfaces │ │ │ │ └── IAppBarContainer.kt │ │ │ ├── main │ │ │ │ └── MainActivity.kt │ │ │ └── tools │ │ │ │ ├── BaseEduActivity.kt │ │ │ │ ├── ExamsActivity.kt │ │ │ │ ├── GradesActivity.kt │ │ │ │ ├── LessonsActivity.kt │ │ │ │ └── VCardActivity.kt │ │ │ ├── utils │ │ │ ├── AppUtils.kt │ │ │ ├── CacheUtils.kt │ │ │ ├── DownloadUtils.kt │ │ │ ├── FileUtils.kt │ │ │ ├── HashUtils.kt │ │ │ ├── JsonUtils.kt │ │ │ ├── MarkdownUtils.kt │ │ │ ├── NetworkStateUtils.kt │ │ │ ├── NotificationUtils.kt │ │ │ ├── OsUtils.kt │ │ │ ├── PackageUtils.kt │ │ │ ├── PermissionUtils.kt │ │ │ ├── QRUtils.kt │ │ │ ├── RandomDataUtils.kt │ │ │ ├── SPDelegates.kt │ │ │ ├── SPUtils.kt │ │ │ ├── TimeUtils.kt │ │ │ ├── Toasty.kt │ │ │ ├── UpdateUtils.kt │ │ │ ├── encrypt │ │ │ │ ├── AesEncryptUtils.kt │ │ │ │ └── DesEncryptUtils.kt │ │ │ ├── extensions │ │ │ │ ├── AnyExtensions.kt │ │ │ │ ├── ByteExtensions.kt │ │ │ │ ├── ContextExtension.kt │ │ │ │ ├── DoubleExtensions.kt │ │ │ │ ├── FileExtensions.kt │ │ │ │ ├── IntExtensions.kt │ │ │ │ ├── StringExtension.kt │ │ │ │ ├── UriExtensions.kt │ │ │ │ └── ViewExtensions.kt │ │ │ ├── lazy │ │ │ │ └── ResettableLazy.kt │ │ │ ├── library │ │ │ │ ├── ReserveUtils.kt │ │ │ │ └── RoomUtils.kt │ │ │ ├── migration │ │ │ │ ├── BaseMigration.kt │ │ │ │ └── Migration.kt │ │ │ ├── status │ │ │ │ ├── AppStatusHelper.kt │ │ │ │ └── OnAppStatusListener.kt │ │ │ ├── tools │ │ │ │ ├── GPAUtils.kt │ │ │ │ ├── GetPortalData.kt │ │ │ │ ├── GradesUtils.kt │ │ │ │ └── VolunteerUtils.kt │ │ │ └── web │ │ │ │ ├── CookieJarImpl.kt │ │ │ │ ├── Requests.kt │ │ │ │ └── WebVPNUtils.kt │ │ │ └── view │ │ │ ├── FullScreenDialogView.kt │ │ │ ├── PasswordPreference.kt │ │ │ └── ToastView.kt │ └── res │ │ ├── drawable-night │ │ └── bg_fs_dialog.xml │ │ ├── drawable-xxxhdpi │ │ ├── bg_toast.xml │ │ └── pic_splash.webp │ │ ├── drawable │ │ ├── bg_fs_dialog.xml │ │ ├── ic_about.xml │ │ ├── ic_about_foreground.xml │ │ ├── ic_cache.xml │ │ ├── ic_card.xml │ │ ├── ic_changelog.xml │ │ ├── ic_color_palette.xml │ │ ├── ic_dark_mode.xml │ │ ├── ic_elec.xml │ │ ├── ic_exams.xml │ │ ├── ic_github.xml │ │ ├── ic_gpa.xml │ │ ├── ic_grades.xml │ │ ├── ic_issue.xml │ │ ├── ic_language.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_lessons.xml │ │ ├── ic_material.xml │ │ ├── ic_multi.xml │ │ ├── ic_passwd.xml │ │ ├── ic_refresh.xml │ │ ├── ic_sc_exams.xml │ │ ├── ic_sc_qr.xml │ │ ├── ic_sc_tool.xml │ │ ├── ic_sc_vcard.xml │ │ ├── ic_single.xml │ │ ├── ic_tools.xml │ │ ├── ic_update.xml │ │ ├── ic_userid.xml │ │ ├── ic_volunteer.xml │ │ ├── simplepage_card.xml │ │ ├── splash_drawable.xml │ │ └── white_back_btn.xml │ │ ├── layout │ │ ├── activity_main_bottom.xml │ │ ├── activity_vcard.xml │ │ ├── dialog_reserve.xml │ │ ├── fragment_single.xml │ │ ├── fragment_yanxiujian.xml │ │ ├── preference_recyclerview.xml │ │ └── simplepage_activity.xml │ │ ├── menu │ │ ├── bottom_nav_menu.xml │ │ └── grade_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── dcmt.webp │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── qhy040404_avatar.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-night-v31 │ │ └── colors.xml │ │ ├── values-night │ │ ├── color_fullscreen.xml │ │ ├── colors.xml │ │ ├── colors_simplepage.xml │ │ ├── themes.xml │ │ └── themes_custom.xml │ │ ├── values-v31 │ │ └── colors.xml │ │ ├── values-v34 │ │ └── bools.xml │ │ ├── values-zh-rCN │ │ ├── arrays.xml │ │ ├── formats.xml │ │ └── strings.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── bools.xml │ │ ├── color_custom.xml │ │ ├── color_fullscreen.xml │ │ ├── colors.xml │ │ ├── colors_material.xml │ │ ├── colors_simplepage.xml │ │ ├── dimens.xml │ │ ├── formats.xml │ │ ├── ids.xml │ │ ├── md_themes.xml │ │ ├── strings.xml │ │ ├── themes.xml │ │ ├── themes_custom.xml │ │ ├── themes_overlay.xml │ │ └── untranslatable.xml │ │ └── xml │ │ ├── filepaths.xml │ │ ├── network_security_config.xml │ │ ├── root_preferences.xml │ │ ├── shortcuts.xml │ │ └── tools_list.xml │ └── test │ ├── README.md │ └── java │ └── com │ └── qhy040404 │ └── libraryonetap │ ├── AesTest.kt │ ├── ConstantsTest.kt │ ├── DatetimeTest.kt │ └── DesTest.kt ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── qodana.yaml ├── settings.gradle.kts └── source └── header.png /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = crlf 6 | indent_size = 2 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.java] 12 | ij_java_use_single_class_imports = true 13 | 14 | [*.{kt,kts}] 15 | ij_kotlin_imports_layout = * 16 | ij_kotlin_allow_trailing_comma = false 17 | ij_kotlin_allow_trailing_comma_on_call_site = false 18 | ktlint_code_style = intellij_idea 19 | ktlint_standard_property-naming = disabled 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | labels: [ "awaiting assessment", "bug" ] 4 | title: "[BUG] " 5 | assignees: [ qhy040404 ] 6 | body: 7 | - type: dropdown 8 | id: channel 9 | attributes: 10 | label: Channel 11 | description: Channel can be found in about page. 12 | options: 13 | - Release 14 | - Pre-release 15 | - Debug 16 | validations: 17 | required: true 18 | - type: input 19 | id: version 20 | attributes: 21 | label: Version 22 | description: Version code that you are using. 23 | placeholder: | 24 | e.g. 3.4.0 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: bug-description 29 | attributes: 30 | label: Bug description 31 | description: What's the bug? 32 | placeholder: | 33 | Application crashed and reporter says there's NPE. 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: expected-behavior 38 | attributes: 39 | label: Expected behavior 40 | description: What did you expect to happen? 41 | placeholder: | 42 | Works fine? 43 | validations: 44 | required: true 45 | - type: textarea 46 | id: actual-behavior 47 | attributes: 48 | label: Actual behavior 49 | description: What happened instead? 50 | placeholder: | 51 | Crashed. 52 | validations: 53 | required: true 54 | - type: textarea 55 | id: steps-to-reproduce 56 | attributes: 57 | label: Steps to reproduce 58 | description: How to reproduce the bug. 59 | placeholder: | 60 | Just do your best. If you cannot reproduce, just left this blank. 61 | - type: input 62 | id: ui 63 | attributes: 64 | label: UI/OS 65 | description: Your system UI or OS. 66 | placeholder: MIUI / HarmonyOS / Native Android / etc. 67 | validations: 68 | required: true 69 | - type: input 70 | id: android 71 | attributes: 72 | label: Android Version 73 | description: Your Android Version 74 | placeholder: "13" 75 | validations: 76 | required: true 77 | - type: textarea 78 | id: additional-info 79 | attributes: 80 | label: Additional info 81 | description: Everything else you consider worthy that we didn't ask for. 82 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/fr.yml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Suggest an idea for this project 3 | labels: [ "awaiting assessment", "enhancement" ] 4 | title: "[FR] " 5 | assignees: [ qhy040404 ] 6 | body: 7 | - type: textarea 8 | id: propose 9 | attributes: 10 | label: Enhancement propose 11 | description: Propose of the enhancement. 12 | placeholder: | 13 | Show your idea here. 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: solution 18 | attributes: 19 | label: Solution 20 | description: What's your solution for this enhancement? 21 | placeholder: | 22 | How to do it on your opinion? Or left this blank 23 | - type: textarea 24 | id: addition 25 | attributes: 26 | label: Additional info 27 | description: Everything else you consider worthy that we didn't ask for. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yml: -------------------------------------------------------------------------------- 1 | name: Other Suggestions 2 | description: Something else? Come here 3 | title: "[OTHER] " 4 | labels: [ "awaiting assessment" ] 5 | assignees: [ qhy040404 ] 6 | body: 7 | - type: textarea 8 | id: other 9 | attributes: 10 | label: Something 11 | description: Optimization? Or what 12 | placeholder: | 13 | I don't know how to write this placeholder. Just say anything. 14 | validations: 15 | required: true 16 | -------------------------------------------------------------------------------- /.github/RELEASE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Changelog 2 | 3 | * This is automated GitHub deployment, human-readable changelog should be available soon. 4 | 5 | --- 6 | 7 | ### [Full Changelog](Changelog.md) 8 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base", 5 | ":assignee(qhy040404)", 6 | ":disableRateLimiting", 7 | ":label(automatic)" 8 | ], 9 | "packageRules": [ 10 | { 11 | "matchUpdateTypes": ["patch", "pin", "digest"], 12 | "automerge": true 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.github/workflows/code_quality.yml: -------------------------------------------------------------------------------- 1 | name: Qodana 2 | on: 3 | workflow_dispatch: 4 | push: 5 | paths-ignore: 6 | - '**.png' 7 | - '**.jpg' 8 | - '.gitattributes' 9 | - '.github/**.json' 10 | - '.gitignore' 11 | - '.gitmodules' 12 | - '**.md' 13 | - 'LICENSE' 14 | - 'NOTICE' 15 | - '.github/workflows/tasks.yml' 16 | - '.github/workflows/publish.yml' 17 | - '.github/workflows/nightly-merge.yml' 18 | 19 | env: 20 | BUGLY_APPID: ${{ secrets.BUGLY_APPID }} 21 | 22 | jobs: 23 | qodana: 24 | runs-on: ubuntu-20.04 25 | steps: 26 | - uses: actions/checkout@v4.1.7 27 | with: 28 | fetch-depth: 0 29 | 30 | - name: Skip duplicate actions 31 | uses: fkirc/skip-duplicate-actions@v5.3.1 32 | with: 33 | cancel_others: true 34 | 35 | - name: Set up java 36 | uses: actions/setup-java@v4.3.0 37 | with: 38 | distribution: 'zulu' 39 | java-version: 17 40 | 41 | - name: Gradle cache 42 | uses: gradle/actions/setup-gradle@v4 43 | 44 | - run: ./gradlew build 45 | 46 | - name: 'Run Qodana' 47 | uses: JetBrains/qodana-action@v2024.1.9 48 | with: 49 | args: > 50 | --env,BUGLY_APPID=${{ secrets.BUGLY_APPID }} 51 | use-caches: false 52 | 53 | - name: Deploy to GitHub Pages 54 | if: github.ref_name == 'master' 55 | uses: peaceiris/actions-gh-pages@v4.0.0 56 | with: 57 | github_token: ${{ secrets.GITHUB_TOKEN }} 58 | publish_dir: ${{ runner.temp }}/qodana/results/report 59 | destination_dir: ./ 60 | 61 | - name: Deploy to GitHub code scanning 62 | if: github.ref_name == 'master' 63 | uses: github/codeql-action/upload-sarif@v3.26.7 64 | with: 65 | sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json 66 | -------------------------------------------------------------------------------- /.github/workflows/gradle-wrapper.yml: -------------------------------------------------------------------------------- 1 | 2 | name: gradle-wrapper 3 | 4 | on: 5 | pull_request: 6 | paths: 7 | - 'gradlew' 8 | - 'gradlew.bat' 9 | - 'gradle/wrapper/' 10 | 11 | jobs: 12 | validate: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: gradle/actions/wrapper-validation@v4 -------------------------------------------------------------------------------- /.github/workflows/nightly-merge.yml: -------------------------------------------------------------------------------- 1 | name: 'Nightly Merge' 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | nightly-merge: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4.1.7 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Nightly Merge 18 | uses: robotology/gh-action-nightly-merge@v1.5.2 19 | with: 20 | stable_branch: 'master' 21 | development_branch: '4.0-dev' 22 | allow_ff: false 23 | user_name: 'qhyBot' 24 | user_email: '104984534+qhyBot@users.noreply.github.com' 25 | push_token: 'QHYBOT_TOKEN' 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | QHYBOT_TOKEN: ${{ secrets.QHYBOT_RELEASE_TOKEN }} 29 | -------------------------------------------------------------------------------- /.github/workflows/tasks.yml: -------------------------------------------------------------------------------- 1 | name: Gradle tasks 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | task: 7 | description: 'Task' 8 | required: true 9 | 10 | env: 11 | BUGLY_APPID: ${{ secrets.BUGLY_APPID }} 12 | 13 | jobs: 14 | tasks: 15 | name: Gradle tasks 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 10 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4.1.7 21 | 22 | - name: Set up java 23 | uses: actions/setup-java@v4.3.0 24 | with: 25 | distribution: 'zulu' 26 | java-version: 17 27 | 28 | - name: Validate Gradle Wrapper 29 | uses: gradle/actions/wrapper-validation@v4 30 | 31 | - name: Gradle cache 32 | uses: gradle/actions/setup-gradle@v4 33 | 34 | - run: ./gradlew ${{ inputs.task }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | *.aab 5 | 6 | # Files for the ART/Dalvik VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # Generated files 13 | bin/ 14 | gen/ 15 | out/ 16 | # Uncomment the following line in case you need and you don't have the release build type files in your app 17 | # release/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | **/build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Proguard folder generated by Eclipse 27 | proguard/ 28 | 29 | # Log Files 30 | *.log 31 | 32 | # Android Studio Navigation editor temp files 33 | .navigation/ 34 | 35 | # Android Studio captures folder 36 | captures/ 37 | 38 | # IntelliJ 39 | *.iml 40 | # Android Studio 3 in .gitignore file. 41 | .idea/caches 42 | .idea/modules.xml 43 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 44 | .idea/navEditor.xml 45 | /.idea/caches 46 | /.idea/libraries 47 | /.idea/workspace.xml 48 | /.idea/navEditor.xml 49 | /.idea/assetWizardSettings.xml 50 | 51 | # Keystore files 52 | # Uncomment the following lines if you do not want to check your keystore files in. 53 | #*.jks 54 | #*.keystore 55 | 56 | # External native build folder generated in Android Studio 2.2 and later 57 | .externalNativeBuild 58 | 59 | # Google Services (e.g. APIs or Firebase) 60 | # google-services.json 61 | 62 | # Freeline 63 | freeline.py 64 | freeline/ 65 | freeline_project_description.json 66 | 67 | # fastlane 68 | fastlane/report.xml 69 | fastlane/Preview.html 70 | fastlane/screenshots 71 | fastlane/test_output 72 | fastlane/readme.md 73 | 74 | # Version control 75 | vcs.xml 76 | 77 | # lint 78 | lint/intermediates/ 79 | lint/generated/ 80 | lint/outputs/ 81 | lint/tmp/ 82 | # lint/reports/ 83 | app/.classpath 84 | app/.project 85 | app/release/** 86 | app/debug/** 87 | 88 | .project 89 | .settings/org.eclipse.buildship.core.prefs 90 | app/.settings/org.eclipse.buildship.core.prefs 91 | .DS_Store 92 | 93 | resources/ 94 | DEPRECATED.txt 95 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | /deploymentTargetDropDown.xml 5 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Library-One-Tap -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/discord.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qhy040404/Library-One-Tap-Android/63799d543e2156c422c5f4c6c8f4bb14cdfb3f25/.idea/icon.png -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /.idea/jsonSchemas.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/migrations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Library-One-Tap-Android 2 | 3 | 一个便于打开离退/暂离页面的小程序,逐渐在成长为一个大工人的小工具箱 4 | 5 | ![header](./source/header.png) 6 | 7 | --- 8 | 9 | ![Alt](https://repobeats.axiom.co/api/embed/cdc58a2fe3912d736bd80f7a85a46d130ce57fb5.svg "Repobeats analytics image") 10 | 11 | [![CodeFactor](https://www.codefactor.io/repository/github/qhy040404/Library-One-Tap-Android/badge)](https://www.codefactor.io/repository/github/qhy040404/Library-One-Tap-Android) 12 | [![Github last commit date](https://img.shields.io/github/last-commit/qhy040404/Library-One-Tap-Android.svg?label=Updated&logo=github&cacheSeconds=600)](https://github.com/qhy040404/Library-One-Tap-Android/commits) 13 | [![License](https://img.shields.io/github/license/qhy040404/Library-One-Tap-Android.svg?label=License&logo=github&cacheSeconds=2592000)](https://github.com/qhy040404/Library-One-Tap-Android/blob/master/LICENSE) 14 | 15 | [![GitHub stable release version](https://img.shields.io/github/v/release/qhy040404/Library-One-Tap-Android.svg?label=Stable&logo=github&cacheSeconds=600)](https://github.com/qhy040404/Library-One-Tap-Android/releases/latest) 16 | [![GitHub stable release date](https://img.shields.io/github/release-date/qhy040404/Library-One-Tap-Android.svg?label=Released&logo=github&cacheSeconds=600)](https://github.com/qhy040404/Library-One-Tap-Android/releases/latest) 17 | 18 | [![GitHub experimental release version](https://img.shields.io/github/v/release/qhy040404/Library-One-Tap-Android?include_prereleases&label=Experimental&logo=github&cacheSeconds=600)](https://github.com/qhy040404/Library-One-Tap-Android/releases) 19 | [![GitHub experimental release date](https://img.shields.io/github/release-date-pre/qhy040404/Library-One-Tap-Android.svg?label=Released&logo=github&cacheSeconds=600)](https://github.com/qhy040404/Library-One-Tap-Android/releases) 20 | 21 | ![GitHub top language](https://img.shields.io/github/languages/top/qhy040404/Library-One-Tap-Android) 22 | 23 | [Changelog](Changelog.md) 24 | 25 | ## 这是什么 26 | 27 | 这个程序最初只是为了在离开图书馆时快速展示离退码或者暂离码,随着慢慢的迭代,逐渐在成为一个适合大工人的工具箱 28 | 29 | ## 适配版本 30 | 31 | Android 10 ~ 13 32 | 33 | iOS 版本部分[源码][ios]在此,版本在 1.0 时期,由于申请 Apple Developer 既费时又费力还费钱,需要的自行完善代码后自签使用 34 | 35 | ## 功能 36 | 37 | * 单人座位签到码、离退码、暂离码 38 | * 研修间签到码 39 | * 工具箱 40 | 41 | ## 友链 42 | 43 | - [约座小程序](https://github.com/qhy040404/DLUT-library-auto-reservation) 44 | - [BeautyYuYanli/DLUT-login](https://github.com/BeautyYuYanli/DLUT-login) (Android 端的校园网登录思路来自这里) 45 | 46 | [ios]:https://github.com/qhy040404/Library-One-Tap-iOS 47 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /release 3 | -------------------------------------------------------------------------------- /app/aapt2-resources.cfg: -------------------------------------------------------------------------------- 1 | color/material_orange_50#no_obfuscate 2 | color/material_indigo_50#no_obfuscate 3 | color/material_blue_grey_50#no_obfuscate 4 | color/material_teal_50#no_obfuscate 5 | color/material_cyan_50#no_obfuscate 6 | color/material_blue_50#no_obfuscate 7 | color/material_blue_grey_50#no_obfuscate 8 | color/material_deep_purple_50#no_obfuscate 9 | 10 | color/material_orange_100#no_obfuscate 11 | color/material_indigo_100#no_obfuscate 12 | color/material_blue_grey_100#no_obfuscate 13 | color/material_teal_100#no_obfuscate 14 | color/material_cyan_100#no_obfuscate 15 | color/material_blue_100#no_obfuscate 16 | color/material_blue_grey_100#no_obfuscate 17 | color/material_deep_purple_100#no_obfuscate 18 | 19 | color/material_orange_600#no_obfuscate 20 | color/material_indigo_600#no_obfuscate 21 | color/material_blue_grey_600#no_obfuscate 22 | color/material_teal_600#no_obfuscate 23 | color/material_cyan_600#no_obfuscate 24 | color/material_blue_600#no_obfuscate 25 | color/material_blue_grey_600#no_obfuscate 26 | color/material_deep_purple_600#no_obfuscate -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 11 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/debug/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 12 | 13 | 14 | 19 | 23 | 24 | 25 | 30 | 34 | 35 | 36 | 41 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/base/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.base 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.pm.ActivityInfo 5 | import android.content.res.Resources 6 | import android.graphics.Color 7 | import android.os.Bundle 8 | import android.view.MenuItem 9 | import androidx.viewbinding.ViewBinding 10 | import com.qhy040404.libraryonetap.LibraryOneTapApp 11 | import com.qhy040404.libraryonetap.R 12 | import com.qhy040404.libraryonetap.constant.GlobalValues 13 | import com.qhy040404.libraryonetap.utils.AppUtils 14 | import rikka.material.app.MaterialActivity 15 | 16 | abstract class BaseActivity : MaterialActivity(), IBinding { 17 | override lateinit var binding: VB 18 | 19 | override fun onCreate(savedInstanceState: Bundle?) { 20 | if (!GlobalValues.md3) { 21 | setTheme(AppUtils.getThemeID(GlobalValues.theme)) 22 | } 23 | 24 | super.onCreate(savedInstanceState) 25 | LibraryOneTapApp.instance?.addActivity(this) 26 | 27 | binding = inflateBinding(layoutInflater).also { 28 | setContentView(it.root) 29 | } 30 | 31 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 32 | init() 33 | } 34 | 35 | @SuppressLint("SourceLockedOrientationActivity") 36 | override fun onResume() { 37 | super.onResume() 38 | requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT 39 | } 40 | 41 | override fun onDestroy() { 42 | super.onDestroy() 43 | LibraryOneTapApp.instance?.removeActivity(this) 44 | } 45 | 46 | override fun shouldApplyTranslucentSystemBars(): Boolean { 47 | return true 48 | } 49 | 50 | override fun computeUserThemeKey(): String? { 51 | return GlobalValues.darkMode + GlobalValues.theme + GlobalValues.md3 52 | } 53 | 54 | override fun onApplyTranslucentSystemBars() { 55 | super.onApplyTranslucentSystemBars() 56 | window.statusBarColor = Color.TRANSPARENT 57 | window.decorView.post { 58 | window.navigationBarColor = Color.TRANSPARENT 59 | window.isNavigationBarContrastEnforced = false 60 | } 61 | } 62 | 63 | override fun onApplyUserThemeResource(theme: Resources.Theme, isDecorView: Boolean) { 64 | theme.applyStyle(R.style.ThemeOverlay, true) 65 | 66 | if (!GlobalValues.md3) { 67 | theme.applyStyle(AppUtils.getThemeID(GlobalValues.theme), true) 68 | } 69 | } 70 | 71 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 72 | finish() 73 | return super.onOptionsItemSelected(item) 74 | } 75 | 76 | protected abstract fun init() 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/base/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.base 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.fragment.app.Fragment 8 | import androidx.viewbinding.ViewBinding 9 | 10 | abstract class BaseFragment : Fragment(), IBinding { 11 | private var _binding: VB? = null 12 | 13 | override val binding: VB 14 | get() = checkNotNull(_binding) { "Binding has been destroyed" } 15 | 16 | override fun onCreateView( 17 | inflater: LayoutInflater, 18 | container: ViewGroup?, 19 | savedInstanceState: Bundle? 20 | ): View { 21 | _binding = inflateBinding(layoutInflater) 22 | return binding.root 23 | } 24 | 25 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 26 | super.onViewCreated(view, savedInstanceState) 27 | init() 28 | } 29 | 30 | override fun onDestroyView() { 31 | _binding = null 32 | super.onDestroyView() 33 | } 34 | 35 | abstract fun init() 36 | 37 | /** 38 | * @see android.app.Activity.runOnUiThread 39 | */ 40 | fun runOnUiThread(action: () -> Unit) = runCatching { requireActivity().runOnUiThread(action) } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/base/BaseViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.base 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/base/IBinding.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.base 2 | 3 | import android.view.LayoutInflater 4 | import androidx.viewbinding.ViewBinding 5 | import java.lang.reflect.Method 6 | import java.lang.reflect.ParameterizedType 7 | 8 | internal sealed interface IBinding { 9 | val binding: VB 10 | 11 | fun inflateBinding(inflater: LayoutInflater): T { 12 | var method: Method? 13 | var clazz: Class<*> = javaClass 14 | while (clazz.superclass != null) { 15 | method = clazz.filterBindingMethod() 16 | if (method == null) { 17 | clazz = clazz.superclass 18 | } else { 19 | @Suppress("UNCHECKED_CAST") 20 | return method.invoke(null, inflater) as T 21 | } 22 | } 23 | error("No Binding type argument found.") 24 | } 25 | 26 | private fun Class<*>.filterBindingMethod(): Method? { 27 | return (genericSuperclass as? ParameterizedType)?.actualTypeArguments 28 | ?.asSequence() 29 | ?.filterIsInstance>() 30 | ?.firstOrNull { it.simpleName.endsWith("Binding") } 31 | ?.getDeclaredMethod("inflate", LayoutInflater::class.java) 32 | ?.also { it.isAccessible = true } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/compat/PMCompat.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.compat 2 | 3 | import android.content.pm.ApplicationInfo 4 | import android.content.pm.PackageInfo 5 | import android.content.pm.PackageManager 6 | import com.qhy040404.libraryonetap.LibraryOneTapApp 7 | import com.qhy040404.libraryonetap.utils.OsUtils 8 | 9 | /** 10 | * Modified Package Manager implementations 11 | */ 12 | object PMCompat { 13 | private val packageManager by lazy { LibraryOneTapApp.app.packageManager } 14 | 15 | fun getPackageInfo(packageName: String, flags: Int): PackageInfo { 16 | return if (OsUtils.atLeastT()) { 17 | packageManager.getPackageInfo( 18 | packageName, 19 | PackageManager.PackageInfoFlags.of(flags.toLong()) 20 | ) 21 | } else { 22 | packageManager.getPackageInfo(packageName, flags) 23 | } 24 | } 25 | 26 | fun getApplicationInfo(packageName: String, flags: Int): ApplicationInfo { 27 | return if (OsUtils.atLeastT()) { 28 | packageManager.getApplicationInfo( 29 | packageName, 30 | PackageManager.ApplicationInfoFlags.of(flags.toLong()) 31 | ) 32 | } else { 33 | packageManager.getApplicationInfo(packageName, flags) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/compat/WICompat.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.compat 2 | 3 | import androidx.core.view.WindowInsetsCompat 4 | import com.qhy040404.libraryonetap.constant.enums.InsetsParams 5 | import com.qhy040404.libraryonetap.utils.OsUtils 6 | 7 | /** 8 | * Modified Window Insets implementations 9 | */ 10 | @Suppress("DEPRECATION") 11 | object WICompat { 12 | fun getSystemBars() = if (OsUtils.atLeastR()) { 13 | WindowInsetsCompat.Type.systemBars() 14 | } else { 15 | 0 16 | } 17 | 18 | fun getInsetsParam( 19 | windowInsets: WindowInsetsCompat, 20 | typeMask: Int, 21 | param: InsetsParams 22 | ) = if (OsUtils.atLeastR()) { 23 | when (param) { 24 | InsetsParams.LEFT -> windowInsets.getInsets(typeMask).left 25 | InsetsParams.RIGHT -> windowInsets.getInsets(typeMask).right 26 | InsetsParams.TOP -> windowInsets.getInsets(typeMask).top 27 | InsetsParams.BOTTOM -> windowInsets.getInsets(typeMask).bottom 28 | } 29 | } else { 30 | when (param) { 31 | InsetsParams.LEFT -> windowInsets.systemWindowInsetLeft 32 | InsetsParams.RIGHT -> windowInsets.systemWindowInsetRight 33 | InsetsParams.TOP -> windowInsets.systemWindowInsetTop 34 | InsetsParams.BOTTOM -> windowInsets.systemWindowInsetBottom 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/constant/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.constant 2 | 3 | object Constants { 4 | const val GLOBAL_ERROR = "Error" 5 | const val NET_TIMEOUT = "Timeout" 6 | const val NET_ERROR = "Network Unknown Error" 7 | const val NET_DISCONNECTED = "Offline" 8 | const val STRING_NULL = "" 9 | 10 | const val PREF_NAME = "name" 11 | const val PREF_ID = "userid" 12 | const val PREF_PASSWD = "passwd" 13 | 14 | const val PREF_GP = "gp_option" 15 | 16 | const val PREF_DARK = "dark" 17 | const val PREF_THEME = "theme" 18 | const val PREF_MD3 = "md3" 19 | const val PREF_LOCALE = "locale" 20 | const val PREF_RESET = "reset" 21 | 22 | const val PREF_CACHE = "cache" 23 | const val PREF_UPDATE = "update" 24 | const val PREF_CHANGELOG = "changelog" 25 | const val PREF_ISSUE = "issue" 26 | const val PREF_ABOUT = "about" 27 | 28 | const val LATEST_APK_NAME = "latestApkName" 29 | 30 | const val DEFAULT_GP = "dlut" 31 | const val DEFAULT_DARK = "system" 32 | const val DEFAULT_THEME = "library" 33 | 34 | const val CONTENT_TYPE_SSO = "application/x-www-form-urlencoded; charset=utf-8" 35 | const val CONTENT_TYPE_JSON = "application/json;charset=UTF-8" 36 | const val CONTENT_TYPE_VCARD = "application/x-www-form-urlencoded" 37 | 38 | const val SHORTCUT_DETAIL = "com.qhy040404.libraryonetap.intent.action.DETAIL" 39 | const val SHORTCUT_TOOLS = "com.qhy040404.libraryonetap.intent.action.TOOLS" 40 | const val SHORTCUT_VCARD = "com.qhy040404.libraryonetap.intent.action.VCARD" 41 | const val SHORTCUT_EXAMS = "com.qhy040404.libraryonetap.intent.action.EXAMS" 42 | 43 | const val LIBRARY_METHOD_IN = "in" 44 | const val LIBRARY_METHOD_OUT = "out" 45 | const val LIBRARY_METHOD_TEMP = "temp" 46 | 47 | const val GPA_DLUT = "dlut" 48 | const val GPA_STANDARD5 = "std5" 49 | const val GPA_STANDARD4 = "std4" 50 | const val GPA_PEKING4 = "peking4" 51 | 52 | const val PORTAL_DEFAULT_POST = "{}" 53 | 54 | const val TOOLS_NET = "net" 55 | const val TOOLS_ELECTRIC = "electric" 56 | const val TOOLS_VCARD = "vcard" 57 | const val TOOLS_VOLUNTEER = "volunteer" 58 | const val TOOLS_GRADES_MAJOR = "major" 59 | const val TOOLS_LESSONS = "lessons" 60 | const val TOOLS_EXAMS = "exams" 61 | 62 | const val RESERVE_VALID = "\"seat_type\":\"1\"" 63 | const val RESERVE_HAS_PERSON = "\"seat_type\":3" 64 | 65 | const val CHANGELOG_INACTIVE = "changelog_html_inactive" 66 | const val CHANGELOG_ACTIVE = "changelog_html_active" 67 | 68 | const val LATEST_GRADE_ID = "latest_grade_id" 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/constant/GlobalManager.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.constant 2 | 3 | import com.qhy040404.libraryonetap.utils.lazy.resettableManager 4 | import com.squareup.moshi.Moshi 5 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 6 | 7 | object GlobalManager { 8 | val lazyMgr = resettableManager() 9 | val moshi: Moshi = Moshi.Builder() 10 | .addLast(KotlinJsonAdapterFactory()) 11 | .build() 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/constant/OnceTag.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.constant 2 | 3 | object OnceTag { 4 | const val FIRST_DATA_INPUT = "InitializeSettings" 5 | const val CLEAR_AFTER_UPDATE = "RemoveApkAfterUpdate" 6 | 7 | const val APP_INITIALIZED = "AppInitialized" 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/constant/enums/GPAAlgorithm.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.constant.enums 2 | 3 | @Suppress("SpellCheckingInspection") 4 | enum class GPAAlgorithm { 5 | DLUT, 6 | STD5, 7 | STD4, 8 | PK4 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/constant/enums/InsetsParams.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.constant.enums 2 | 3 | enum class InsetsParams { 4 | LEFT, 5 | RIGHT, 6 | TOP, 7 | BOTTOM 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/constant/enums/OrderModes.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.constant.enums 2 | 3 | enum class OrderModes { 4 | YANXIUJIAN, 5 | DETAIL; 6 | 7 | override fun toString(): String { 8 | return (values().indexOf(this) + 1).toString() 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/constant/enums/Parentheses.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.constant.enums 2 | 3 | enum class Parentheses { 4 | SMALL, 5 | MEDIUM, 6 | LARGE 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/CancelDTO.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data 2 | 3 | data class CancelDTO( 4 | val success: Boolean, 5 | val message: String? = null 6 | ) 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/ElectricDTO.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data 2 | 3 | data class ElectricDTO( 4 | val dormitoryInfo_list: List? = null, 5 | val flag: String 6 | ) { 7 | @Suppress("SpellCheckingInspection") 8 | data class ElectricInnerDTO( 9 | val SSMC: String? = null, 10 | val ZSBH: String? = null, 11 | val flag: String, 12 | val resele: String? = null 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/GithubAPIDTO.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data 2 | 3 | @Suppress("SpellCheckingInspection") 4 | data class GithubAPIDTO( 5 | val assets: List, 6 | val assets_url: String, 7 | val author: Author, 8 | val body: String, 9 | val created_at: String, 10 | val draft: Boolean, 11 | val html_url: String, 12 | val id: Int, 13 | val name: String, 14 | val node_id: String, 15 | val prerelease: Boolean, 16 | val published_at: String, 17 | val tag_name: String, 18 | val tarball_url: String, 19 | val target_commitish: String, 20 | val upload_url: String, 21 | val url: String, 22 | val zipball_url: String 23 | ) { 24 | data class Asset( 25 | val browser_download_url: String, 26 | val content_type: String, 27 | val created_at: String, 28 | val download_count: Int, 29 | val id: Int, 30 | val label: String, 31 | val name: String, 32 | val node_id: String, 33 | val size: Int, 34 | val state: String, 35 | val updated_at: String, 36 | val uploader: Uploader, 37 | val url: String 38 | ) 39 | 40 | data class Author( 41 | val avatar_url: String, 42 | val events_url: String, 43 | val followers_url: String, 44 | val following_url: String, 45 | val gists_url: String, 46 | val gravatar_id: String, 47 | val html_url: String, 48 | val id: Int, 49 | val login: String, 50 | val node_id: String, 51 | val organizations_url: String, 52 | val received_events_url: String, 53 | val repos_url: String, 54 | val site_admin: Boolean, 55 | val starred_url: String, 56 | val subscriptions_url: String, 57 | val type: String, 58 | val url: String 59 | ) 60 | 61 | data class Uploader( 62 | val avatar_url: String, 63 | val events_url: String, 64 | val followers_url: String, 65 | val following_url: String, 66 | val gists_url: String, 67 | val gravatar_id: String, 68 | val html_url: String, 69 | val id: Int, 70 | val login: String, 71 | val node_id: String, 72 | val organizations_url: String, 73 | val received_events_url: String, 74 | val repos_url: String, 75 | val site_admin: Boolean, 76 | val starred_url: String, 77 | val subscriptions_url: String, 78 | val type: String, 79 | val url: String 80 | ) 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/NetDTO.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data 2 | 3 | data class NetDTO( 4 | val fee: String? = null, 5 | val dynamicRemainFlow: String? = null, 6 | val dynamicUsedFlow: String? = null, 7 | val flag: String 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/ReserveDTO.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data 2 | 3 | data class ReserveDTO( 4 | val data: ReserveInnerDTO? = null, 5 | val success: Boolean 6 | ) { 7 | data class ReserveInnerDTO( 8 | val addCode: String? = null, 9 | val area_name: String? = null, 10 | val attention_text: String? = null, 11 | val open_end: String? = null, 12 | val open_start: String? = null, 13 | val order_start: String? = null, 14 | val room_id: String? = null, 15 | val room_name: String? = null, 16 | val rule_cancel_time: String? = null, 17 | val rule_check_time: String? = null, 18 | val seat_id: String? = null, 19 | val seat_label: String? = null 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/SessionDTO.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data 2 | 3 | data class SessionDTO( 4 | val message: String, 5 | val success: Boolean, 6 | val user_id: String 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/VCardStatusDTO.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data 2 | 3 | data class VCardStatusDTO( 4 | val message: String, 5 | val resultData: ResultData, 6 | val success: Boolean 7 | ) { 8 | 9 | @Suppress("SpellCheckingInspection") 10 | data class ResultData( 11 | val message: String, 12 | val paytime: String, 13 | val status: String 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/VolunteerDTO.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data 2 | 3 | data class VolunteerDTO( 4 | val numSameID: Int = 0, 5 | val numSameName: Int = 0, 6 | val totalDuration: Double = 0.0 7 | ) 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/tools/Exam.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data.tools 2 | 3 | import com.qhy040404.datetime.Datetime 4 | 5 | data class Exam( 6 | val name: String, 7 | val time: String, 8 | val startTime: Datetime, 9 | val endTime: Datetime, 10 | val room: String 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/tools/Grade.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data.tools 2 | 3 | data class Grade( 4 | val id: Int, 5 | val name: String, 6 | val code: String, 7 | val credit: Double, 8 | val grade: String, 9 | val gp: Double, 10 | val type: String 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/tools/Lesson.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data.tools 2 | 3 | data class Lesson( 4 | val id: Int, 5 | val code: String, 6 | val compulsory: String, 7 | val name: String, 8 | val credit: Double, 9 | val teacher: String, 10 | val type: String 11 | ) 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/data/tools/Semester.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.data.tools 2 | 3 | data class Semester( 4 | val id: Int, 5 | val name: String, 6 | val courses: List 7 | ) { 8 | var needsEvaluationTasks = mutableListOf() 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/Card.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | data class Card( 4 | val content: CharSequence, 5 | val lineSpacingExtra: Int = 0 6 | ) 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/CardView.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | import android.content.Context 4 | import android.util.TypedValue 5 | import android.widget.LinearLayout 6 | import androidx.appcompat.widget.AppCompatTextView 7 | import com.absinthe.libraries.utils.extensions.dp 8 | import com.qhy040404.libraryonetap.R 9 | import com.qhy040404.libraryonetap.utils.extensions.getColor 10 | import com.qhy040404.libraryonetap.utils.extensions.getDrawable 11 | 12 | class CardView(context: Context) : LinearLayout(context) { 13 | val content = AppCompatTextView(context).apply { 14 | layoutParams = LayoutParams( 15 | LayoutParams.WRAP_CONTENT, 16 | LayoutParams.WRAP_CONTENT 17 | ) 18 | setPadding(paddingLeft, paddingTop, paddingRight, 8.dp) 19 | setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f) 20 | setTextColor(R.color.simple_page_card_text_color.getColor(context)) 21 | setTextIsSelectable(true) 22 | } 23 | 24 | init { 25 | layoutParams = LayoutParams( 26 | LayoutParams.MATCH_PARENT, 27 | LayoutParams.WRAP_CONTENT 28 | ) 29 | setPadding(16.dp, 16.dp, 12.dp, 16.dp) 30 | background = R.drawable.simplepage_card.getDrawable(context) 31 | elevation = 1.dp.toFloat() 32 | 33 | addView(content) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/CardViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.drakeet.multitype.ItemViewBinder 6 | import com.qhy040404.libraryonetap.base.BaseViewHolder 7 | 8 | class CardViewBinder : ItemViewBinder() { 9 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): BaseViewHolder { 10 | return BaseViewHolder( 11 | CardView(inflater.context) 12 | ) 13 | } 14 | 15 | override fun onBindViewHolder(holder: BaseViewHolder, item: Card) { 16 | (holder.itemView as CardView).content.apply { 17 | setLineSpacing( 18 | item.lineSpacingExtra.toFloat(), 19 | (holder.itemView as CardView).content.lineSpacingMultiplier 20 | ) 21 | text = item.content 22 | } 23 | } 24 | 25 | override fun getItemId(item: Card): Long { 26 | return item.hashCode().toLong() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/Category.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | data class Category( 4 | val title: String 5 | ) 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/CategoryView.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | import android.content.Context 4 | import android.view.Gravity 5 | import android.widget.LinearLayout 6 | import androidx.appcompat.widget.AppCompatTextView 7 | import androidx.core.view.marginBottom 8 | import androidx.core.view.marginTop 9 | import com.absinthe.libraries.utils.extensions.dp 10 | import com.qhy040404.libraryonetap.R 11 | import com.qhy040404.libraryonetap.utils.extensions.getColor 12 | 13 | class CategoryView(context: Context) : LinearLayout(context) { 14 | val title = AppCompatTextView(context).apply { 15 | layoutParams = LayoutParams( 16 | 0.dp, 17 | LayoutParams.WRAP_CONTENT 18 | ).apply { 19 | setMargins(1.dp, marginTop, 1.dp, marginBottom) 20 | weight = 1f 21 | } 22 | setPadding( 23 | 16.dp, 24 | 12.dp, 25 | 12.dp, 26 | 12.dp 27 | ) 28 | setTextColor(R.color.simple_page_category_text_color.getColor(context)) 29 | } 30 | 31 | init { 32 | layoutParams = LayoutParams( 33 | LayoutParams.MATCH_PARENT, 34 | LayoutParams.WRAP_CONTENT 35 | ) 36 | gravity = Gravity.CENTER_VERTICAL 37 | orientation = HORIZONTAL 38 | 39 | addView(title) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/CategoryViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.drakeet.multitype.ItemViewBinder 6 | import com.qhy040404.libraryonetap.base.BaseViewHolder 7 | 8 | class CategoryViewBinder : ItemViewBinder() { 9 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): BaseViewHolder { 10 | return BaseViewHolder( 11 | CategoryView(inflater.context) 12 | ) 13 | } 14 | 15 | override fun onBindViewHolder(holder: BaseViewHolder, item: Category) { 16 | (holder.itemView as CategoryView).title.text = item.title 17 | } 18 | 19 | override fun getItemId(item: Category): Long { 20 | return item.hashCode().toLong() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/Clickable.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | data class Clickable( 4 | val name: String, 5 | val desc: String, 6 | val url: String? = null, 7 | val passed: Boolean = true 8 | ) 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/ClickableView.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.view.View 6 | import android.widget.LinearLayout 7 | import androidx.appcompat.widget.AppCompatTextView 8 | import androidx.core.net.toUri 9 | import com.absinthe.libraries.utils.extensions.dp 10 | import com.qhy040404.libraryonetap.R 11 | import com.qhy040404.libraryonetap.utils.extensions.getColor 12 | import com.qhy040404.libraryonetap.utils.extensions.getDrawable 13 | 14 | class ClickableView(context: Context) : LinearLayout(context), View.OnClickListener { 15 | val name = AppCompatTextView(context).apply { 16 | layoutParams = LayoutParams( 17 | LayoutParams.WRAP_CONTENT, 18 | LayoutParams.WRAP_CONTENT 19 | ) 20 | setTextColor(R.color.simple_page_name_color.getColor(context)) 21 | } 22 | 23 | val desc = AppCompatTextView(context).apply { 24 | layoutParams = LayoutParams( 25 | LayoutParams.WRAP_CONTENT, 26 | LayoutParams.WRAP_CONTENT 27 | ) 28 | setTextColor(R.color.simple_page_desc_color.getColor(context)) 29 | } 30 | 31 | var url: String? = null 32 | 33 | init { 34 | layoutParams = LayoutParams( 35 | LayoutParams.MATCH_PARENT, 36 | LayoutParams.WRAP_CONTENT 37 | ) 38 | orientation = VERTICAL 39 | setPadding(16.dp, 16.dp, 12.dp, 16.dp) 40 | background = R.drawable.simplepage_card.getDrawable(context) 41 | elevation = 1.dp.toFloat() 42 | 43 | addView(name) 44 | addView(desc) 45 | } 46 | 47 | override fun onClick(v: View) { 48 | url?.let { 49 | val intent = Intent(Intent.ACTION_VIEW) 50 | intent.data = it.toUri() 51 | runCatching { 52 | context.startActivity(intent) 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/ClickableViewBinder.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import com.drakeet.multitype.ItemViewBinder 6 | import com.qhy040404.libraryonetap.R 7 | import com.qhy040404.libraryonetap.base.BaseViewHolder 8 | import com.qhy040404.libraryonetap.utils.extensions.getColor 9 | 10 | class ClickableViewBinder : ItemViewBinder() { 11 | override fun onCreateViewHolder(inflater: LayoutInflater, parent: ViewGroup): BaseViewHolder { 12 | return BaseViewHolder( 13 | ClickableView(inflater.context) 14 | ) 15 | } 16 | 17 | override fun onBindViewHolder(holder: BaseViewHolder, item: Clickable) { 18 | (holder.itemView as ClickableView).apply { 19 | name.text = item.name 20 | desc.text = item.desc 21 | url = item.url 22 | if (!item.passed) { 23 | name.setTextColor(R.color.red_500.getColor(context)) 24 | } 25 | } 26 | } 27 | 28 | override fun getItemId(item: Clickable): Long { 29 | return item.hashCode().toLong() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/recyclerview/simplepage/DividerItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.recyclerview.simplepage 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | import androidx.recyclerview.widget.RecyclerView.ItemDecoration 7 | import com.drakeet.multitype.MultiTypeAdapter 8 | 9 | class DividerItemDecoration(private val adapter: MultiTypeAdapter) : ItemDecoration() { 10 | private val dividerClasses = arrayOf(Card::class.java, Clickable::class.java) 11 | override fun getItemOffsets( 12 | outRect: Rect, 13 | view: View, 14 | parent: RecyclerView, 15 | state: RecyclerView.State 16 | ) { 17 | if (adapter.itemCount == 0) { 18 | outRect.set(0, 0, 0, 0) 19 | return 20 | } 21 | val items: List<*> = adapter.items 22 | val position = parent.getChildAdapterPosition(view) 23 | val should = position + 1 < items.size && 24 | dividerClasses.contains(items[position]!!.javaClass) && 25 | dividerClasses.contains(items[position + 1]!!.javaClass) 26 | if (should) { 27 | outRect.set(0, 0, 0, 1) 28 | } else { 29 | outRect.set(0, 0, 0, 0) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/ui/about/ClickableViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.ui.about; 2 | 3 | import android.content.ActivityNotFoundException; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.view.View; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.browser.customtabs.CustomTabsIntent; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | /** 13 | * @author drakeet 14 | * @author qhy040404 15 | */ 16 | public class ClickableViewHolder extends RecyclerView.ViewHolder { 17 | private @Nullable 18 | String url; 19 | 20 | public ClickableViewHolder(View itemView) { 21 | super(itemView); 22 | itemView.setOnClickListener(v -> { 23 | if (url != null) { 24 | try { 25 | new CustomTabsIntent.Builder().build() 26 | .launchUrl(v.getContext(), Uri.parse(url)); 27 | } catch (ActivityNotFoundException ignored1) { 28 | Intent intent = new Intent(Intent.ACTION_VIEW); 29 | intent.setData(Uri.parse(url)); 30 | try { 31 | v.getContext().startActivity(intent); 32 | } catch (ActivityNotFoundException ignored2) { 33 | } 34 | } 35 | } 36 | }); 37 | } 38 | 39 | public void setURL(@Nullable String url) { 40 | this.url = url; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/ui/about/ContributorViewBinder.java: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.ui.about; 2 | 3 | import static android.net.Uri.parse; 4 | 5 | import android.content.ActivityNotFoundException; 6 | import android.content.Intent; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.browser.customtabs.CustomTabsIntent; 15 | import androidx.recyclerview.widget.RecyclerView; 16 | 17 | import com.drakeet.about.Contributor; 18 | import com.drakeet.about.OnContributorClickedListener; 19 | import com.drakeet.about.R; 20 | import com.drakeet.multitype.ItemViewBinder; 21 | 22 | /** 23 | * @author drakeet 24 | * @author qhy040404 25 | */ 26 | public class ContributorViewBinder extends ItemViewBinder { 27 | private @NonNull 28 | final AbsAboutActivityProxy activity; 29 | 30 | public ContributorViewBinder(@NonNull AbsAboutActivityProxy activity) { 31 | this.activity = activity; 32 | } 33 | 34 | @Override 35 | @NonNull 36 | public CBViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { 37 | return new CBViewHolder(inflater.inflate(R.layout.about_page_item_contributor, parent, false), activity); 38 | } 39 | 40 | @Override 41 | public void onBindViewHolder(@NonNull CBViewHolder holder, @NonNull Contributor contributor) { 42 | holder.avatar.setImageResource(contributor.avatarResId); 43 | holder.name.setText(contributor.name); 44 | holder.desc.setText(contributor.desc); 45 | holder.data = contributor; 46 | } 47 | 48 | @Override 49 | public long getItemId(@NonNull Contributor item) { 50 | return item.hashCode(); 51 | } 52 | 53 | public static class CBViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 54 | public final ImageView avatar; 55 | public final TextView name; 56 | public final TextView desc; 57 | 58 | protected @NonNull 59 | final AbsAboutActivityProxy activity; 60 | 61 | public Contributor data; 62 | 63 | public CBViewHolder(View itemView, @NonNull AbsAboutActivityProxy activity) { 64 | super(itemView); 65 | this.activity = activity; 66 | avatar = itemView.findViewById(R.id.avatar); 67 | name = itemView.findViewById(R.id.name); 68 | desc = itemView.findViewById(R.id.desc); 69 | itemView.setOnClickListener(this); 70 | } 71 | 72 | @Override 73 | public void onClick(View v) { 74 | OnContributorClickedListener listener = activity.getOnContributorClickedListener(); 75 | if (listener != null && listener.onContributorClicked(v, data)) { 76 | return; 77 | } 78 | if (data.url != null) { 79 | try { 80 | new CustomTabsIntent.Builder().build() 81 | .launchUrl(v.getContext(), parse(data.url)); 82 | } catch (ActivityNotFoundException ignored1) { 83 | Intent intent = new Intent(Intent.ACTION_VIEW); 84 | intent.setData(parse(data.url)); 85 | try { 86 | v.getContext().startActivity(intent); 87 | } catch (ActivityNotFoundException ignored2) { 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/ui/about/DividerItemDecoration.java: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.ui.about; 2 | 3 | import android.graphics.Rect; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | import com.drakeet.about.License; 11 | import com.drakeet.about.Recommendation; 12 | import com.drakeet.multitype.MultiTypeAdapter; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * @author drakeet 18 | * @author qhy040404 19 | */ 20 | class DividerItemDecoration extends RecyclerView.ItemDecoration { 21 | private final @NonNull 22 | MultiTypeAdapter adapter; 23 | 24 | private final Class[] dividerClasses = {License.class, Recommendation.class}; 25 | 26 | /** 27 | * Creates a divider {@link RecyclerView.ItemDecoration} that can be used with a 28 | * {@link LinearLayoutManager}. 29 | * 30 | * @param adapter The MultiTypeAdapter 31 | */ 32 | DividerItemDecoration(@NonNull MultiTypeAdapter adapter) { 33 | this.adapter = adapter; 34 | } 35 | 36 | @Override 37 | public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { 38 | if (adapter.getItemCount() == 0) { 39 | outRect.set(0, 0, 0, 0); 40 | return; 41 | } 42 | List items = adapter.getItems(); 43 | int position = parent.getChildAdapterPosition(view); 44 | boolean should = false; 45 | for (int i = 0; !should && i < dividerClasses.length; i++) { 46 | should = position + 1 < items.size() 47 | && items.get(position).getClass().isAssignableFrom(dividerClasses[i]) 48 | && items.get(position + 1).getClass().isAssignableFrom(dividerClasses[i]); 49 | } 50 | if (should) { 51 | outRect.set(0, 0, 0, 1); 52 | } else { 53 | outRect.set(0, 0, 0, 0); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/ui/about/LicenseViewBinder.java: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.ui.about; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | 11 | import com.drakeet.about.License; 12 | import com.drakeet.about.R; 13 | import com.drakeet.multitype.ItemViewBinder; 14 | 15 | /** 16 | * @author drakeet 17 | * @author qhy040404 18 | */ 19 | public class LicenseViewBinder extends ItemViewBinder { 20 | @Override 21 | @NonNull 22 | public LViewHolder onCreateViewHolder(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) { 23 | return new LViewHolder(inflater.inflate(R.layout.about_page_item_license, parent, false)); 24 | } 25 | 26 | @Override 27 | @SuppressLint("SetTextI18n") 28 | public void onBindViewHolder(@NonNull LViewHolder holder, @NonNull License data) { 29 | holder.content.setText(data.name + " - " + data.author); 30 | holder.hint.setText(data.url + "\n" + data.type); 31 | holder.setURL(data.url); 32 | } 33 | 34 | @Override 35 | public long getItemId(@NonNull License item) { 36 | return item.hashCode(); 37 | } 38 | 39 | public static class LViewHolder extends ClickableViewHolder { 40 | public final TextView content; 41 | public final TextView hint; 42 | 43 | public LViewHolder(View itemView) { 44 | super(itemView); 45 | content = itemView.findViewById(R.id.content); 46 | hint = itemView.findViewById(R.id.hint); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/ui/fragment/fullscreen/FullScreenDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.ui.fragment.fullscreen 2 | 3 | import android.graphics.Color 4 | import android.graphics.drawable.ColorDrawable 5 | import android.os.Bundle 6 | import android.view.Gravity 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.fragment.app.DialogFragment 11 | import com.qhy040404.libraryonetap.LibraryOneTapApp 12 | import com.qhy040404.libraryonetap.view.FullScreenDialogView 13 | 14 | class FullScreenDialogFragment(private val message: String) : DialogFragment() { 15 | override fun onCreate(savedInstanceState: Bundle?) { 16 | super.onCreate(savedInstanceState) 17 | LibraryOneTapApp.instance?.addFragment(this) 18 | } 19 | 20 | override fun onCreateView( 21 | inflater: LayoutInflater, 22 | container: ViewGroup?, 23 | savedInstanceState: Bundle? 24 | ): View { 25 | dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 26 | return FullScreenDialogView(requireContext()).apply { 27 | detail.text = message 28 | } 29 | } 30 | 31 | override fun onResume() { 32 | super.onResume() 33 | dialog?.apply { 34 | run { 35 | setCanceledOnTouchOutside(isCancelable) 36 | setCancelable(isCancelable) 37 | } 38 | window?.run { 39 | attributes = attributes?.apply { 40 | width = ViewGroup.LayoutParams.MATCH_PARENT 41 | height = ViewGroup.LayoutParams.MATCH_PARENT 42 | gravity = Gravity.BOTTOM 43 | } 44 | } 45 | } 46 | } 47 | 48 | override fun isCancelable() = false 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/ui/interfaces/IAppBarContainer.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.ui.interfaces 2 | 3 | import android.view.View 4 | 5 | interface IAppBarContainer { 6 | fun scheduleAppbarLiftingStatus(isLifted: Boolean) 7 | fun setLiftOnScrollTargetView(targetView: View) 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/ui/tools/BaseEduActivity.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.ui.tools 2 | 3 | import androidx.annotation.StringRes 4 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 5 | import com.qhy040404.libraryonetap.R 6 | import com.qhy040404.libraryonetap.constant.GlobalValues 7 | import com.qhy040404.libraryonetap.constant.URLManager 8 | import com.qhy040404.libraryonetap.recyclerview.SimplePageActivity 9 | import com.qhy040404.libraryonetap.utils.web.Requests 10 | 11 | abstract class BaseEduActivity : SimplePageActivity() { 12 | protected var currentVisible = true 13 | 14 | fun showInitFailedAlertDialog(@StringRes title: Int) { 15 | MaterialAlertDialogBuilder(this) 16 | .setTitle(title) 17 | .setMessage(GlobalValues.ssoPrompt) 18 | .setPositiveButton(R.string.glb_ok) { _, _ -> 19 | finish() 20 | } 21 | .setCancelable(false) 22 | .create().also { 23 | if (this.currentVisible) { 24 | it.show() 25 | } 26 | } 27 | } 28 | 29 | fun initMinor() { 30 | if (GlobalValues.majorStuId == 0 || GlobalValues.minorStuId == 0) { 31 | val initUrl = Requests.get(URLManager.EDU_GRADE_INIT_URL, getUrl = true) 32 | val initData = Requests.get(URLManager.EDU_GRADE_INIT_URL) 33 | 34 | if (initUrl.contains("semester-index")) { 35 | GlobalValues.majorStuId = initUrl.substringAfter("semester-index/").toInt() 36 | GlobalValues.minorStuId = -1 37 | } else { 38 | val initList = initData.split("onclick=\"myFunction(this)\" value=\"") 39 | if (initList.size == 3) { 40 | val aStuId = initList[1].substringBefore("\"").toInt() 41 | val bStuId = initList[2].substringBefore("\"").toInt() 42 | GlobalValues.majorStuId = aStuId.coerceAtMost(bStuId) 43 | GlobalValues.minorStuId = aStuId.coerceAtLeast(bStuId) 44 | } 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/AppUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import android.content.Context 4 | import android.content.res.Configuration 5 | import androidx.annotation.StringRes 6 | import androidx.appcompat.app.AppCompatDelegate 7 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 8 | import com.qhy040404.libraryonetap.R 9 | import com.qhy040404.libraryonetap.constant.Constants 10 | 11 | object AppUtils { 12 | fun getNightMode(modeString: String) = when (modeString) { 13 | "on" -> AppCompatDelegate.MODE_NIGHT_YES 14 | "off" -> AppCompatDelegate.MODE_NIGHT_NO 15 | else -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM 16 | } 17 | 18 | fun getThemeID(theme: String): Int { 19 | return when (theme) { 20 | "purple" -> R.style.Theme_Purple_NoActionBar 21 | "library" -> R.style.Theme_Main_NoActionBar 22 | "blue" -> R.style.Theme_Blue_NoActionBar 23 | "pink" -> R.style.Theme_Pink_NoActionBar 24 | "green" -> R.style.Theme_Green_NoActionBar 25 | "orange" -> R.style.Theme_Orange_NoActionBar 26 | "red" -> R.style.Theme_Red_NoActionBar 27 | "simple" -> R.style.Theme_Simple_NoActionBar 28 | else -> getThemeID(RandomDataUtils.randomTheme) 29 | } 30 | } 31 | 32 | fun checkData( 33 | id: String, 34 | passwd: String, 35 | context: Context? = null, 36 | @StringRes titleId: Int? = null, 37 | @StringRes messageId: Int? = null 38 | ): Boolean { 39 | (id != "Error" && passwd != "Error" && id.isNotEmpty() && passwd.isNotEmpty()).let { 40 | if (!it && context != null && titleId != null && messageId != null) { 41 | MaterialAlertDialogBuilder(context) 42 | .setTitle(titleId) 43 | .setMessage(messageId) 44 | .setPositiveButton(R.string.glb_ok, null) 45 | .setCancelable(true) 46 | .create() 47 | .show() 48 | } 49 | return it 50 | } 51 | } 52 | 53 | fun currentIsNightMode(context: Context): Boolean { 54 | val uiMode = context.resources.configuration.uiMode 55 | return when (uiMode and Configuration.UI_MODE_NIGHT_MASK) { 56 | Configuration.UI_MODE_NIGHT_YES -> true 57 | else -> false 58 | } 59 | } 60 | 61 | fun hasNetwork(): Boolean { 62 | return NetworkStateUtils.checkNetworkType() != Constants.GLOBAL_ERROR 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/CacheUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import com.qhy040404.libraryonetap.LibraryOneTapApp 4 | import java.io.File 5 | import java.text.DecimalFormat 6 | 7 | object CacheUtils { 8 | private val externalCache = LibraryOneTapApp.app.externalCacheDir 9 | private val cache = LibraryOneTapApp.app.cacheDir 10 | private val codeCache = LibraryOneTapApp.app.codeCacheDir 11 | private val df = DecimalFormat("#.00") 12 | 13 | fun getCacheSize() = formatFileSize( 14 | FileUtils.getFolderSize(externalCache!!) + 15 | FileUtils.getFolderSize(cache) + 16 | FileUtils.getFolderSize(codeCache) 17 | ) 18 | 19 | private fun formatFileSize(fileSize: Long) = if (fileSize == 0L) { 20 | "0.00 K" 21 | } else if (fileSize < 1024) { 22 | df.format(fileSize.toDouble()) + " B" 23 | } else if (fileSize < 1024 * 1024) { 24 | df.format(fileSize.toDouble() / 1024) + " K" 25 | } else if (fileSize < 1024 * 1024 * 1024) { 26 | df.format(fileSize.toDouble() / (1024 * 1024)) + " M" 27 | } else { 28 | df.format(fileSize.toDouble() / (1024 * 1024 * 1024)) + " G" 29 | } 30 | 31 | fun trimCaches() { 32 | trimCache(cache) 33 | trimCache(codeCache) 34 | trimCache(externalCache) 35 | } 36 | 37 | private fun trimCache(file: File?) = runCatching { 38 | if (file != null && file.isDirectory && file.listFiles()!!.isNotEmpty()) { 39 | FileUtils.delete(file) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/FileUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import java.io.File 4 | 5 | object FileUtils { 6 | fun delete(file: File?): Boolean { 7 | if (file == null) return false 8 | return if (file.isDirectory) { 9 | deleteDir(file) 10 | } else { 11 | deleteFile(file) 12 | } 13 | } 14 | 15 | private fun deleteDir(dir: File?): Boolean { 16 | if (dir == null) return false 17 | if (!dir.exists()) return true 18 | if (!dir.isDirectory) return false 19 | val files = dir.listFiles() 20 | if (files != null && files.isNotEmpty()) { 21 | for (file in files) { 22 | if (file.isFile) { 23 | if (!file.delete()) return false 24 | } else if (file.isDirectory) { 25 | if (!deleteDir(file)) return false 26 | } 27 | } 28 | } 29 | return dir.delete() 30 | } 31 | 32 | private fun deleteFile(file: File?): Boolean { 33 | return file != null && (!file.exists() || file.isFile && file.delete()) 34 | } 35 | 36 | fun getFolderSize(file: File): Long { 37 | var size = 0L 38 | runCatching { 39 | val fileList = file.listFiles() 40 | if (fileList != null) { 41 | for (i in fileList.indices) { 42 | size += if (fileList[i].isDirectory) { 43 | getFolderSize(fileList[i]) 44 | } else { 45 | fileList[i].length() 46 | } 47 | } 48 | } 49 | } 50 | return size 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/HashUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import com.qhy040404.libraryonetap.utils.extensions.toHex 4 | import java.security.MessageDigest 5 | 6 | object HashUtils { 7 | fun md5(byteArray: ByteArray): String = digest(byteArray, "MD5") 8 | 9 | fun sha1(byteArray: ByteArray): String = digest(byteArray, "SHA-1") 10 | 11 | fun sha256(byteArray: ByteArray): String = digest(byteArray, "SHA-256") 12 | 13 | fun sha512(byteArray: ByteArray): String = digest(byteArray, "SHA-512") 14 | 15 | private fun digest(byteArray: ByteArray, algorithm: String): String { 16 | val messageDigest = MessageDigest.getInstance(algorithm).apply { 17 | reset() 18 | } 19 | messageDigest.update(byteArray) 20 | return messageDigest.digest().toHex() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/JsonUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import com.qhy040404.libraryonetap.constant.GlobalManager.moshi 4 | import org.intellij.lang.annotations.Language 5 | 6 | object JsonUtils { 7 | @Language("JSON") 8 | inline fun toJson(value: T?): String? = runCatching { 9 | moshi.adapter(T::class.java).toJson(value) 10 | }.getOrNull() 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/MarkdownUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor 4 | import org.intellij.markdown.html.HtmlGenerator 5 | import org.intellij.markdown.parser.MarkdownParser 6 | 7 | object MarkdownUtils { 8 | /** 9 | * Parse markdown to HTML 10 | * 11 | * @param src markdown 12 | * @return parsed HTML string 13 | */ 14 | fun fromString(src: String): String { 15 | val flavour = CommonMarkFlavourDescriptor() 16 | val parsedTree = MarkdownParser(flavour).buildMarkdownTreeFromString(src) 17 | return HtmlGenerator(src, parsedTree, flavour).generateHtml() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/NetworkStateUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import android.net.ConnectivityManager 4 | import android.net.NetworkCapabilities 5 | import androidx.core.content.getSystemService 6 | import com.qhy040404.libraryonetap.LibraryOneTapApp 7 | import com.qhy040404.libraryonetap.constant.Constants 8 | 9 | object NetworkStateUtils { 10 | fun checkNetworkType(): String { 11 | val manager = LibraryOneTapApp.app.getSystemService() 12 | ?: return Constants.GLOBAL_ERROR 13 | val networkCapabilities = 14 | manager.getNetworkCapabilities(manager.activeNetwork) ?: return Constants.GLOBAL_ERROR 15 | return when { 16 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> "WIFI" 17 | networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> "Cellular" 18 | else -> Constants.GLOBAL_ERROR 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/OsUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import android.os.Build 4 | import androidx.annotation.ChecksSdkIntAtLeast 5 | 6 | object OsUtils { 7 | @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU) 8 | fun atLeastT(): Boolean { 9 | return Build.VERSION.SDK_INT >= 33 10 | } 11 | 12 | @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S) 13 | fun atLeastS(): Boolean { 14 | return Build.VERSION.SDK_INT >= 31 15 | } 16 | 17 | @ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R) 18 | fun atLeastR(): Boolean { 19 | return Build.VERSION.SDK_INT >= 30 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/PackageUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import android.content.pm.ApplicationInfo 4 | import android.content.pm.PackageInfo 5 | import android.content.pm.PackageManager 6 | import com.qhy040404.libraryonetap.LibraryOneTapApp 7 | import com.qhy040404.libraryonetap.compat.PMCompat 8 | 9 | object PackageUtils { 10 | private val packInfo: PackageInfo = 11 | PMCompat.getPackageInfo(LibraryOneTapApp.app.packageName, 0) 12 | private val appInfo: ApplicationInfo = PMCompat.getApplicationInfo( 13 | LibraryOneTapApp.app.packageName, 14 | PackageManager.GET_META_DATA 15 | ) 16 | 17 | val versionCode = packInfo.longVersionCode 18 | val versionName: String = packInfo.versionName 19 | 20 | val buildType = appInfo.metaData.getString("Channel").toString() 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/PermissionUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import android.app.Activity 4 | import android.content.pm.PackageManager 5 | import androidx.annotation.StringRes 6 | import androidx.fragment.app.FragmentManager 7 | import com.qhy040404.libraryonetap.ui.fragment.fullscreen.FullScreenDialogFragment 8 | import com.qhy040404.libraryonetap.utils.extensions.getString 9 | 10 | object PermissionUtils { 11 | fun checkPermission( 12 | activity: Activity, 13 | permission: String, 14 | childFragmentMgr: FragmentManager, 15 | @StringRes strId: Int 16 | ): Boolean { 17 | return if (activity.checkSelfPermission(permission) == PackageManager.PERMISSION_DENIED) { 18 | FullScreenDialogFragment(strId.getString()).apply { 19 | show(childFragmentMgr, null) 20 | } 21 | activity.requestPermissions(arrayOf(permission), 100) 22 | false 23 | } else { 24 | true 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/QRUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Canvas 5 | import android.graphics.Color 6 | import android.graphics.Paint 7 | import android.graphics.RectF 8 | 9 | object QRUtils { 10 | fun toGrayscale(bmpOriginal: Bitmap): Bitmap { 11 | val width = bmpOriginal.width 12 | val height = bmpOriginal.height 13 | var color: Int 14 | var r: Int 15 | var g: Int 16 | var b: Int 17 | var a: Int 18 | 19 | val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) 20 | 21 | val oldPx = IntArray(width * height) 22 | val newPx = IntArray(width * height) 23 | bmpOriginal.getPixels(oldPx, 0, width, 0, 0, width, height) 24 | 25 | for (j in 0 until width * height) { 26 | color = oldPx[j] 27 | r = Color.red(color) 28 | g = Color.green(color) 29 | b = Color.blue(color) 30 | a = Color.alpha(color) 31 | var gray = (r.toFloat() * 0.3 + g.toFloat() * 0.59 + b.toFloat() * 0.11).toInt() 32 | gray = if (gray < 128) { 33 | 0 34 | } else { 35 | 255 36 | } 37 | newPx[j] = Color.argb(a, gray, gray, gray) 38 | } 39 | 40 | bmp.setPixels(newPx, 0, width, 0, 0, width, height) 41 | return createWhiteBorderBitmap(bmp) 42 | } 43 | 44 | fun createWhiteBorderBitmap(bmp: Bitmap, width: Int = 10, radius: Float = 5F): Bitmap { 45 | val outBmp = Bitmap.createBitmap( 46 | bmp.width + width * 2, 47 | bmp.height + width * 2, 48 | Bitmap.Config.ARGB_8888 49 | ) 50 | val canvas = Canvas(outBmp) 51 | 52 | val rectF = RectF(0F, 0F, outBmp.width.toFloat(), outBmp.height.toFloat()) 53 | 54 | val paint = Paint() 55 | paint.isAntiAlias = true 56 | paint.color = Color.WHITE 57 | 58 | canvas.drawRoundRect(rectF, radius, radius, paint) 59 | canvas.drawBitmap(bmp, width.toFloat(), width.toFloat(), paint) 60 | 61 | return outBmp 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/RandomDataUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import com.qhy040404.libraryonetap.constant.GlobalManager 4 | import com.qhy040404.libraryonetap.utils.lazy.resettableLazy 5 | import java.util.Random 6 | 7 | object RandomDataUtils { 8 | fun getNum(numCount: Int) = if (numCount > 0) { 9 | Random().nextInt(numCount) 10 | } else { 11 | 0 12 | } 13 | 14 | val randomTheme by resettableLazy(GlobalManager.lazyMgr) { 15 | when (getNum(8)) { 16 | 0 -> "purple" 17 | 1 -> "library" 18 | 2 -> "blue" 19 | 3 -> "pink" 20 | 4 -> "green" 21 | 5 -> "orange" 22 | 6 -> "red" 23 | else -> "simple" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/SPDelegates.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import kotlin.properties.ReadWriteProperty 4 | import kotlin.reflect.KProperty 5 | 6 | class SPDelegates(private val key: String, private val default: T) : ReadWriteProperty { 7 | override fun getValue(thisRef: Any?, property: KProperty<*>) = 8 | SPUtils.getValue(key, default) 9 | 10 | override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) = 11 | SPUtils.setValue(key, value) 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/SPUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.qhy040404.libraryonetap.LibraryOneTapApp 6 | import com.qhy040404.libraryonetap.constant.GlobalValues.SP_NAME 7 | import com.qhy040404.libraryonetap.utils.lazy.resettableLazy 8 | import com.qhy040404.libraryonetap.utils.lazy.resettableManager 9 | 10 | object SPUtils { 11 | val spLazyMgr = resettableManager() 12 | val sp: SharedPreferences by resettableLazy(spLazyMgr) { 13 | LibraryOneTapApp.app.getSharedPreferences( 14 | SP_NAME, 15 | Context.MODE_PRIVATE 16 | ) 17 | } 18 | 19 | fun getValue(name: String, default: T, confirm: Boolean = false): T { 20 | val value = with(sp) { 21 | val res: Any = when (default) { 22 | is Long -> getLong(name, default) 23 | is String -> getString(name, default).orEmpty() 24 | is Int -> getInt(name, default) 25 | is Boolean -> getBoolean(name, default) 26 | is Float -> getFloat(name, default) 27 | else -> throw IllegalArgumentException("Unknown data type") 28 | } 29 | @Suppress("UNCHECKED_CAST") 30 | res as T 31 | } 32 | if (!confirm && value == default) { 33 | spLazyMgr.reset() 34 | return getValue(name, default, true) 35 | } 36 | return value 37 | } 38 | 39 | fun setValue(name: String, value: T) = with(sp.edit()) { 40 | when (value) { 41 | is Long -> putLong(name, value) 42 | is String -> putString(name, value) 43 | is Int -> putInt(name, value) 44 | is Boolean -> putBoolean(name, value) 45 | is Float -> putFloat(name, value) 46 | else -> throw IllegalArgumentException("This type can't be saved into Preferences") 47 | }.apply() 48 | } 49 | 50 | fun resetAll() = with(sp.edit()) { 51 | clear().apply() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/TimeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import com.qhy040404.datetime.Datetime 4 | import com.qhy040404.libraryonetap.utils.extensions.one2two 5 | 6 | object TimeUtils { 7 | fun getToday(separator: String, doubleNum: Boolean): String { 8 | val now = Datetime.now() 9 | val year = now.year.toString() 10 | val month = if (doubleNum) { 11 | now.month.one2two() 12 | } else { 13 | now.month.toString() 14 | } 15 | val day = if (doubleNum) { 16 | now.day.one2two() 17 | } else { 18 | now.day.toString() 19 | } 20 | return "$year$separator$month$separator$day" 21 | } 22 | 23 | fun isValidReserveTime(): Boolean { 24 | return Datetime.now().let { 25 | it.hour in 7..22 || it.hour == 6 && it.minute > 30 26 | } 27 | } 28 | 29 | fun isServerAvailableTime(): Boolean { 30 | return Datetime.now().hour in 6 until 23 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/Toasty.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils 2 | 3 | import android.content.Context 4 | import android.os.Handler 5 | import android.os.Looper 6 | import android.view.ContextThemeWrapper 7 | import android.view.Gravity 8 | import android.widget.Toast 9 | import androidx.annotation.AnyThread 10 | import androidx.annotation.MainThread 11 | import androidx.annotation.StringRes 12 | import com.qhy040404.libraryonetap.view.ToastView 13 | import java.lang.ref.WeakReference 14 | 15 | object Toasty { 16 | private val handler = Handler(Looper.getMainLooper()) 17 | private var toastRef: WeakReference? = null 18 | 19 | fun cancel() = toastRef?.get()?.cancel() 20 | 21 | @AnyThread 22 | fun showShort(context: Context, message: String) { 23 | if (Looper.getMainLooper() == Looper.myLooper()) { 24 | //noinspection WrongThread 25 | show(context, message, Toast.LENGTH_SHORT) 26 | } else { 27 | handler.post { show(context, message, Toast.LENGTH_SHORT) } 28 | } 29 | } 30 | 31 | @AnyThread 32 | fun showShort(context: Context, @StringRes res: Int) = 33 | showShort(context, context.getString(res)) 34 | 35 | @AnyThread 36 | fun showLong(context: Context, message: String) { 37 | if (Looper.getMainLooper() == Looper.myLooper()) { 38 | //noinspection WrongThread 39 | show(context, message, Toast.LENGTH_LONG) 40 | } else { 41 | handler.post { show(context, message, Toast.LENGTH_LONG) } 42 | } 43 | } 44 | 45 | @AnyThread 46 | fun showLong(context: Context, @StringRes res: Int) = 47 | showLong(context, context.getString(res)) 48 | 49 | @Suppress("DEPRECATION") 50 | @MainThread 51 | private fun show(context: Context, message: String, duration: Int) { 52 | cancel() 53 | 54 | val toast = if (OsUtils.atLeastR() && context !is ContextThemeWrapper) { 55 | Toast(context).also { 56 | it.duration = duration 57 | it.setText(message) 58 | } 59 | } else { 60 | val view = ToastView(context).also { 61 | it.message.text = message 62 | } 63 | Toast(context).also { 64 | it.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 200) 65 | it.duration = duration 66 | it.view = view 67 | } 68 | } 69 | toastRef = WeakReference(toast) 70 | toast.show() 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/encrypt/AesEncryptUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.encrypt 2 | 3 | import android.annotation.SuppressLint 4 | import android.util.Base64 5 | import com.qhy040404.libraryonetap.utils.extensions.toHex 6 | import java.security.Security 7 | import javax.crypto.Cipher 8 | import javax.crypto.spec.IvParameterSpec 9 | import javax.crypto.spec.SecretKeySpec 10 | import org.spongycastle.jce.provider.BouncyCastleProvider 11 | 12 | object AesEncryptUtils { 13 | private const val cipherMode = "AES/CFB/NoPadding" 14 | 15 | private fun encrypt(text: String, key: String, iv: String): String { 16 | val sKeySpec = SecretKeySpec(key.toByteArray(), "AES") 17 | val cipher = Cipher.getInstance(cipherMode) 18 | cipher.init(Cipher.ENCRYPT_MODE, sKeySpec, IvParameterSpec(iv.toByteArray())) 19 | return cipher.doFinal(text.toByteArray()).toHex() 20 | } 21 | 22 | fun vpnEncrypt(text: String, key: String, iv: String): String { 23 | return iv.toByteArray().toHex() + encrypt(text, key, iv) 24 | } 25 | 26 | @SuppressLint("GetInstance") 27 | object VCard { 28 | private const val vCardCipherMode = "AES/ECB/PKCS7Padding" 29 | 30 | init { 31 | Security.addProvider(BouncyCastleProvider()) 32 | } 33 | 34 | fun genKey(template: Int = 16): String { 35 | val map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 36 | return buildString { 37 | repeat(template) { 38 | append(map[(Math.random() * map.length).toInt()]) 39 | } 40 | } 41 | } 42 | 43 | fun encrypt(data: String, key: String): String { 44 | return Cipher.getInstance(vCardCipherMode, "SC").apply { 45 | init(Cipher.ENCRYPT_MODE, SecretKeySpec(key.toByteArray(Charsets.UTF_8), "AES")) 46 | }.let { cipher -> 47 | Base64.encodeToString( 48 | cipher.doFinal(data.toByteArray(Charsets.UTF_8)), 49 | Base64.DEFAULT 50 | ) 51 | } 52 | } 53 | 54 | fun decrypt(data: String, key: String): String { 55 | return Cipher.getInstance(vCardCipherMode, "SC").apply { 56 | init(Cipher.DECRYPT_MODE, SecretKeySpec(key.toByteArray(Charsets.UTF_8), "AES")) 57 | }.doFinal(Base64.decode(data, Base64.DEFAULT)).decodeToString() 58 | } 59 | 60 | fun handleKey(data: String, reversed: Boolean): String { 61 | return if (reversed) { 62 | (data.substring(10) + data.substring(0, 10)).reversed() 63 | } else { 64 | data.reversed().let { 65 | it.substring(6) + it.substring(0, 6) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/extensions/AnyExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.extensions 2 | 3 | import com.qhy040404.libraryonetap.utils.JsonUtils 4 | 5 | /** 6 | * Convert any data class to json string 7 | * 8 | * @return JSON String 9 | */ 10 | fun Any?.toJson(): String? = JsonUtils.toJson(this) 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/extensions/ByteExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.extensions 2 | 3 | import com.qhy040404.libraryonetap.utils.HashUtils 4 | 5 | /** 6 | * Calculate a ByteArray's MD5 hash 7 | * 8 | * @return MD5 hash of the ByteArray 9 | */ 10 | fun ByteArray.md5() = HashUtils.md5(this) 11 | 12 | /** 13 | * Calculate a ByteArray's SHA-1 hash 14 | * 15 | * @return SHA-1 hash of the ByteArray 16 | */ 17 | fun ByteArray.sha1() = HashUtils.sha1(this) 18 | 19 | /** 20 | * Calculate a ByteArray's SHA-256 hash 21 | * 22 | * @return SHA-256 hash of the ByteArray 23 | */ 24 | fun ByteArray.sha256() = HashUtils.sha256(this) 25 | 26 | /** 27 | * Calculate a ByteArray's SHA-512 hash 28 | * 29 | * @return SHA-512 hash of the ByteArray 30 | */ 31 | fun ByteArray.sha512() = HashUtils.sha512(this) 32 | 33 | /** 34 | * Convert a ByteArray to Hex String 35 | * 36 | * @return HexString of the ByteArray 37 | */ 38 | fun ByteArray.toHex(): String { 39 | return buildString { 40 | this@toHex.forEach { 41 | var hex = Integer.toHexString(it.toInt() and 0xFF) 42 | if (hex.length == 1) { 43 | hex = "0$hex" 44 | } 45 | append(hex.lowercase()) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/extensions/ContextExtension.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.extensions 2 | 3 | import android.content.Context 4 | import androidx.annotation.AnyThread 5 | import androidx.annotation.StringRes 6 | import com.qhy040404.libraryonetap.utils.Toasty 7 | 8 | /** 9 | * Show short-time toast with String 10 | * 11 | * @param message original string 12 | */ 13 | @AnyThread 14 | fun Context.showToast(message: String) = Toasty.showShort(this, message) 15 | 16 | /** 17 | * Show short-time toast with String 18 | * 19 | * @param res string's resource id 20 | */ 21 | @AnyThread 22 | fun Context.showToast(@StringRes res: Int) = Toasty.showShort(this, res) 23 | 24 | /** 25 | * Show long-time toast with String 26 | * 27 | * @param message original string 28 | */ 29 | @AnyThread 30 | fun Context.showLongToast(message: String) = Toasty.showLong(this, message) 31 | 32 | /** 33 | * Show long-time toast with String 34 | * 35 | * @param res string's resource id 36 | */ 37 | @AnyThread 38 | fun Context.showLongToast(@StringRes res: Int) = Toasty.showLong(this, res) 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/extensions/DoubleExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.extensions 2 | 3 | import java.math.BigDecimal 4 | import java.math.RoundingMode 5 | 6 | /** 7 | * Keep two decimal places after rounding 8 | * 9 | * @return Double with two decimal 10 | */ 11 | fun Double.to2Decimal(): Double { 12 | return runCatching { 13 | BigDecimal.valueOf(this).setScale(2, RoundingMode.HALF_UP).toDouble() 14 | }.getOrDefault(0.0) 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/extensions/FileExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.extensions 2 | 3 | import com.qhy040404.libraryonetap.utils.HashUtils 4 | import java.io.File 5 | 6 | /** 7 | * Calculate a File's MD5 hash 8 | * 9 | * @return MD5 hash of the File 10 | */ 11 | fun File.md5() = HashUtils.md5(this.readBytes()) 12 | 13 | /** 14 | * Calculate a File's SHA-1 hash 15 | * 16 | * @return SHA-1 hash of the File 17 | */ 18 | fun File.sha1() = HashUtils.sha1(this.readBytes()) 19 | 20 | /** 21 | * Calculate a File's SHA-256 hash 22 | * 23 | * @return SHA-256 hash of the File 24 | */ 25 | fun File.sha256() = HashUtils.sha256(this.readBytes()) 26 | 27 | /** 28 | * Calculate a File's SHA-512 hash 29 | * 30 | * @return SHA-512 hash of the File 31 | */ 32 | fun File.sha512() = HashUtils.sha512(this.readBytes()) 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/extensions/IntExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.extensions 2 | 3 | import android.content.Context 4 | import android.graphics.drawable.Drawable 5 | import com.qhy040404.libraryonetap.LibraryOneTapApp 6 | import com.qhy040404.libraryonetap.constant.GlobalValues 7 | 8 | /** 9 | * Get string from resource ID 10 | */ 11 | fun Int.getString(): String { 12 | val context = LibraryOneTapApp.app 13 | val conf = context.resources.configuration.apply { 14 | setLocale(GlobalValues.locale) 15 | } 16 | return context.createConfigurationContext(conf).getString(this) 17 | } 18 | 19 | /** 20 | * Get format string from resource ID and format with provided params 21 | */ 22 | fun Int.getStringAndFormat(vararg params: Any?): String { 23 | return this.getString().format(*params) 24 | } 25 | 26 | /** 27 | * Get color from resource ID 28 | */ 29 | fun Int.getColor(context: Context): Int { 30 | return context.getColor(this) 31 | } 32 | 33 | /** 34 | * Get drawable from resource ID 35 | */ 36 | fun Int.getDrawable(context: Context): Drawable? { 37 | return context.getDrawable(this) 38 | } 39 | 40 | /** 41 | * Get dimen from resource ID 42 | */ 43 | fun Int.getDimen(): Float { 44 | return LibraryOneTapApp.app.resources.getDimension(this) 45 | } 46 | 47 | /** 48 | * Convert an Integer from one digit to two digits 49 | */ 50 | fun Int.one2two(): String { 51 | return if (this >= 10) { 52 | "$this" 53 | } else { 54 | "0$this" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/extensions/UriExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.extensions 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import androidx.browser.customtabs.CustomTabsIntent 7 | 8 | /** 9 | * Try to start Uri with CustomTabsIntent, if failed, start it with traditional Intent 10 | * 11 | * @param context Context 12 | */ 13 | fun Uri.start( 14 | context: Context, 15 | onFailure: () -> Unit = { 16 | val intent = Intent(Intent.ACTION_VIEW).apply { 17 | data = this@start 18 | } 19 | runCatching { 20 | context.startActivity(intent) 21 | } 22 | } 23 | ) { 24 | runCatching { 25 | CustomTabsIntent.Builder().build() 26 | .launchUrl(context, this) 27 | }.onFailure { 28 | onFailure() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/extensions/ViewExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.extensions 2 | 3 | import android.animation.Animator 4 | import android.animation.TimeInterpolator 5 | import android.animation.ValueAnimator 6 | import android.content.Context 7 | import android.graphics.BitmapFactory 8 | import android.view.animation.AccelerateDecelerateInterpolator 9 | import androidx.appcompat.widget.AppCompatImageView 10 | import androidx.viewpager2.widget.ViewPager2 11 | import coil.load 12 | import com.qhy040404.libraryonetap.utils.AppUtils 13 | import com.qhy040404.libraryonetap.utils.QRUtils 14 | 15 | fun ViewPager2.setCurrentItem( 16 | item: Int, 17 | duration: Long, 18 | interpolator: TimeInterpolator = AccelerateDecelerateInterpolator(), 19 | pagePxWidth: Int = width 20 | ) { 21 | val pxToDrag: Int = pagePxWidth * (item - currentItem) 22 | val animator = ValueAnimator.ofInt(0, pxToDrag) 23 | var previousValue = 0 24 | animator.addUpdateListener { valueAnimator -> 25 | val currentValue = valueAnimator.animatedValue as Int 26 | val currentPxToDrag = (currentValue - previousValue).toFloat() 27 | fakeDragBy(-currentPxToDrag) 28 | previousValue = currentValue 29 | } 30 | animator.addListener(object : Animator.AnimatorListener { 31 | override fun onAnimationStart(animation: Animator) { 32 | beginFakeDrag() 33 | } 34 | 35 | override fun onAnimationEnd(animation: Animator) { 36 | endFakeDrag() 37 | } 38 | 39 | override fun onAnimationCancel(animation: Animator) {} 40 | override fun onAnimationRepeat(animation: Animator) {} 41 | }) 42 | animator.interpolator = interpolator 43 | animator.duration = duration 44 | animator.start() 45 | } 46 | 47 | /** 48 | * Auto detect night mode to generate a nicer QR code for ImageView 49 | * 50 | * @param context Context 51 | * @param origByteArray Original QR code byte array 52 | */ 53 | fun AppCompatImageView.mLoad(context: Context, origByteArray: ByteArray) { 54 | val qrCode = BitmapFactory.decodeByteArray(origByteArray, 0, origByteArray.size) 55 | load( 56 | if (AppUtils.currentIsNightMode(context)) { 57 | QRUtils.createWhiteBorderBitmap(qrCode, 2) 58 | } else { 59 | qrCode 60 | } 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/lazy/ResettableLazy.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.lazy 2 | 3 | import java.util.LinkedList 4 | import kotlin.reflect.KProperty 5 | 6 | fun resettableLazy( 7 | manager: ResettableLazyManager, 8 | init: () -> T 9 | ): ResettableLazy = ResettableLazy(manager, init) 10 | 11 | fun resettableManager(): ResettableLazyManager = ResettableLazyManager() 12 | 13 | interface Resettable { 14 | fun reset() 15 | } 16 | 17 | class ResettableLazy( 18 | private val manager: ResettableLazyManager, 19 | val init: () -> T 20 | ) : Resettable { 21 | @Volatile 22 | var lazyHolder = makeInitBlock() 23 | 24 | operator fun getValue(thisRef: Any?, property: KProperty<*>): T = lazyHolder.value 25 | 26 | override fun reset() { 27 | lazyHolder = makeInitBlock() 28 | } 29 | 30 | private fun makeInitBlock(): Lazy { 31 | return lazy { 32 | manager.register(this) 33 | init() 34 | } 35 | } 36 | } 37 | 38 | class ResettableLazyManager { 39 | private val managedDelegates = LinkedList() 40 | 41 | fun register(managed: Resettable) { 42 | synchronized(managedDelegates) { 43 | managedDelegates.add(managed) 44 | } 45 | } 46 | 47 | fun reset() { 48 | synchronized(managedDelegates) { 49 | managedDelegates.forEach { it.reset() } 50 | managedDelegates.clear() 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/library/ReserveUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.library 2 | 3 | import com.qhy040404.libraryonetap.utils.TimeUtils 4 | import com.qhy040404.libraryonetap.utils.extensions.substringBetween 5 | 6 | object ReserveUtils { 7 | fun getResetRoomCode(space: String): Int { 8 | val area = space.substringBefore("图书馆") 9 | val room = space.substringBetween("图书馆", "阅") 10 | return RoomUtils.getRoomCode(area, room) 11 | } 12 | 13 | fun constructPara(room: Int) = "room_id=$room&order_date=${TimeUtils.getToday("/", false)}" 14 | 15 | fun constructParaForAddCode(seatId: String) = 16 | "seat_id=$seatId&order_date=${TimeUtils.getToday("/", false)}" 17 | 18 | fun constructParaForFinalReserve(addCode: String) = "addCode=$addCode&method=addSeat" 19 | 20 | fun formatAvailableMap(am: String) = am.trim() 21 | .replace("[[", "") 22 | .replace("]]", "") 23 | .replace("{", "") 24 | .replace("}", "") 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/library/RoomUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.library 2 | 3 | import com.qhy040404.libraryonetap.utils.RandomDataUtils 4 | 5 | object RoomUtils { 6 | fun getRoomCode(area: String, room: String) = when (area) { 7 | "伯川", "Bochuan" -> when (room) { 8 | "301" -> 168 9 | "312" -> 170 10 | "401" -> 195 11 | "404" -> 197 12 | "409" -> 196 13 | "501" -> 198 14 | "504" -> 199 15 | "507" -> 200 16 | else -> { 17 | val temp = intArrayOf(168, 170, 195, 197, 196, 198, 199, 200) 18 | temp[RandomDataUtils.getNum(8)] 19 | } 20 | } 21 | 22 | "令希", "Lingxi" -> when (room) { 23 | "301" -> 207 24 | "302" -> 208 25 | "401" -> 205 26 | "402" -> 206 27 | "501" -> 203 28 | "502" -> 204 29 | "601" -> 201 30 | "602" -> 202 31 | else -> { 32 | val temp = intArrayOf(201, 202, 203, 204, 205, 206, 207, 208) 33 | temp[RandomDataUtils.getNum(8)] 34 | } 35 | } 36 | 37 | else -> 0 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/migration/BaseMigration.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.migration 2 | 3 | import jonathanfinerty.once.Once 4 | 5 | abstract class BaseMigration(private val commit: String) { 6 | protected abstract val reason: String 7 | protected abstract fun migrate() 8 | 9 | fun doMigration() { 10 | if (Once.beenDone(commit)) return 11 | migrate() 12 | Once.markDone(commit) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/migration/Migration.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.migration 2 | 3 | import com.qhy040404.libraryonetap.LibraryOneTapApp 4 | import com.qhy040404.libraryonetap.constant.Constants 5 | import java.io.File 6 | 7 | object Migration { 8 | fun checkAndMigrate() { 9 | MIGRATION_CHANGELOG.doMigration() 10 | } 11 | 12 | private val MIGRATION_CHANGELOG = object : BaseMigration("5e22380b") { 13 | override val reason = "Local changelog was saved as HTML string, new version uses " + 14 | "MarkdownParser to parse changelog. As a result, an Exception will be thrown because " + 15 | "of MarkdownParser cannot parse a HTML string." 16 | 17 | override fun migrate() { 18 | File(LibraryOneTapApp.app.dataDir, Constants.CHANGELOG_INACTIVE).delete() 19 | File(LibraryOneTapApp.app.dataDir, Constants.CHANGELOG_ACTIVE).delete() 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/status/AppStatusHelper.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.status 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import android.os.Bundle 6 | 7 | object AppStatusHelper { 8 | private var mOnAppStatusListener: OnAppStatusListener? = null 9 | 10 | fun register(app: Application, listener: OnAppStatusListener) { 11 | mOnAppStatusListener = listener 12 | app.registerActivityLifecycleCallbacks(activityLifecycleCallbacks) 13 | } 14 | 15 | private val activityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks { 16 | private var activityStartCount = 0 17 | override fun onActivityCreated(p0: Activity, p1: Bundle?) {} 18 | override fun onActivityStarted(p0: Activity) { 19 | activityStartCount++ 20 | if (activityStartCount == 1) { 21 | mOnAppStatusListener?.onFront() 22 | } 23 | } 24 | 25 | override fun onActivityResumed(p0: Activity) {} 26 | override fun onActivityPaused(p0: Activity) {} 27 | override fun onActivityStopped(p0: Activity) { 28 | activityStartCount-- 29 | if (activityStartCount == 0) { 30 | mOnAppStatusListener?.onBack() 31 | } 32 | } 33 | 34 | override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {} 35 | override fun onActivityDestroyed(p0: Activity) {} 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/status/OnAppStatusListener.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.status 2 | 3 | interface OnAppStatusListener { 4 | fun onFront() 5 | fun onBack() 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/tools/GPAUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.tools 2 | 3 | import com.qhy040404.libraryonetap.constant.enums.GPAAlgorithm 4 | import com.qhy040404.libraryonetap.data.tools.Grade 5 | import com.qhy040404.libraryonetap.utils.extensions.to2Decimal 6 | 7 | object GPAUtils { 8 | fun calculateGPA(grades: List, algorithm: GPAAlgorithm): Double { 9 | var totalWeightedGP = 0.0 10 | var totalCredits = grades.sumOf { it.credit } 11 | grades.forEach { 12 | runCatching { 13 | totalWeightedGP += when (algorithm) { 14 | GPAAlgorithm.DLUT -> it.gp * it.credit 15 | GPAAlgorithm.STD5 -> getGpByStandard5(it.grade.toInt()) * it.credit 16 | GPAAlgorithm.STD4 -> getGpByStandard4(it.grade.toInt()) * it.credit 17 | GPAAlgorithm.PK4 -> getGpByPeking4(it.grade.toInt()) * it.credit 18 | } 19 | }.onFailure { _ -> 20 | totalCredits -= it.credit 21 | } 22 | } 23 | return (totalWeightedGP / totalCredits).to2Decimal() 24 | } 25 | 26 | private fun getGpByStandard5(score: Int): Double { 27 | return when (score) { 28 | in 95..100 -> 5.0 29 | in 90 until 95 -> 4.5 30 | in 85 until 90 -> 4.0 31 | in 80 until 85 -> 3.5 32 | in 75 until 80 -> 3.0 33 | in 70 until 75 -> 2.5 34 | in 65 until 70 -> 2.0 35 | in 60 until 65 -> 1.0 36 | else -> 0.0 37 | } 38 | } 39 | 40 | private fun getGpByStandard4(score: Int): Double { 41 | return when (score) { 42 | in 90..100 -> 4.0 43 | in 80 until 90 -> 3.0 44 | in 70 until 80 -> 2.0 45 | in 60 until 70 -> 1.0 46 | else -> 0.0 47 | } 48 | } 49 | 50 | private fun getGpByPeking4(score: Int): Double { 51 | return when (score) { 52 | in 90..100 -> 4.0 53 | in 85 until 90 -> 3.7 54 | in 82 until 85 -> 3.3 55 | in 78 until 82 -> 3.0 56 | in 75 until 78 -> 2.7 57 | in 72 until 75 -> 2.3 58 | in 68 until 72 -> 2.0 59 | in 64 until 68 -> 1.5 60 | in 60 until 64 -> 1.0 61 | else -> 0.0 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/tools/GetPortalData.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.tools 2 | 3 | import com.qhy040404.libraryonetap.constant.Constants 4 | import com.qhy040404.libraryonetap.constant.GlobalValues 5 | import com.qhy040404.libraryonetap.constant.URLManager 6 | import com.qhy040404.libraryonetap.utils.web.Requests 7 | 8 | object GetPortalData { 9 | /** 10 | * mode 0:electric 11 | * mode 1:net 12 | */ 13 | fun getPortalData(mode: Int): String { 14 | Requests.loginSso( 15 | URLManager.PORTAL_SSO_URL, 16 | GlobalValues.ctSso, 17 | URLManager.PORTAL_SSO_URL 18 | ).let { 19 | if (it.not()) { 20 | return GlobalValues.ssoPrompt 21 | } 22 | } 23 | Requests.get(URLManager.PORTAL_DIRECT_URL) 24 | 25 | return when (mode) { 26 | 0 -> Requests.post( 27 | URLManager.PORTAL_ELEC_URL, 28 | Constants.PORTAL_DEFAULT_POST, 29 | GlobalValues.ctJson 30 | ) 31 | 32 | 1 -> Requests.post( 33 | URLManager.PORTAL_NET_URL, 34 | Constants.PORTAL_DEFAULT_POST, 35 | GlobalValues.ctJson 36 | ) 37 | 38 | else -> throw IllegalArgumentException("Unknown mode") 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/tools/GradesUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.tools 2 | 3 | import android.content.Context 4 | import com.absinthe.libraries.utils.extensions.getStringArray 5 | import com.qhy040404.libraryonetap.R 6 | import com.qhy040404.libraryonetap.constant.Constants 7 | import com.qhy040404.libraryonetap.constant.GlobalValues 8 | import com.qhy040404.libraryonetap.constant.enums.GPAAlgorithm 9 | import com.qhy040404.libraryonetap.constant.enums.Parentheses 10 | import com.qhy040404.libraryonetap.data.tools.Grade 11 | import com.qhy040404.libraryonetap.utils.extensions.addParentheses 12 | import com.qhy040404.libraryonetap.utils.extensions.to2Decimal 13 | 14 | object GradesUtils { 15 | fun calculateWeightedAverage( 16 | grades: List 17 | ): Double { 18 | var totalWeightedSum = 0.0 19 | var totalCredits = grades.sumOf { it.credit } 20 | grades.forEach { 21 | runCatching { 22 | totalWeightedSum += it.grade.toDouble() * it.credit 23 | }.onFailure { _ -> 24 | totalCredits -= it.credit 25 | } 26 | } 27 | return (totalWeightedSum / totalCredits).to2Decimal() 28 | } 29 | 30 | fun calculateAverageGP( 31 | context: Context, 32 | grades: List 33 | ): String { 34 | return when (GlobalValues.gpOption) { 35 | Constants.GPA_DLUT -> { 36 | GPAUtils.calculateGPA(grades, GPAAlgorithm.DLUT) 37 | .toString() + getCurrentGPAAlgorithm(context).addParentheses(Parentheses.SMALL) 38 | } 39 | 40 | Constants.GPA_STANDARD5 -> { 41 | GPAUtils.calculateGPA(grades, GPAAlgorithm.STD5) 42 | .toString() + getCurrentGPAAlgorithm(context).addParentheses(Parentheses.SMALL) 43 | } 44 | 45 | Constants.GPA_STANDARD4 -> { 46 | GPAUtils.calculateGPA(grades, GPAAlgorithm.STD4) 47 | .toString() + getCurrentGPAAlgorithm(context).addParentheses(Parentheses.SMALL) 48 | } 49 | 50 | Constants.GPA_PEKING4 -> { 51 | GPAUtils.calculateGPA(grades, GPAAlgorithm.PK4) 52 | .toString() + getCurrentGPAAlgorithm(context).addParentheses(Parentheses.SMALL) 53 | } 54 | 55 | else -> { 56 | GPAUtils.calculateGPA(grades, GPAAlgorithm.DLUT) 57 | .toString() + getCurrentGPAAlgorithm(context).addParentheses(Parentheses.SMALL) 58 | } 59 | } 60 | } 61 | 62 | private fun getCurrentGPAAlgorithm(context: Context): String = 63 | context.getStringArray(R.array.gp)[ 64 | context.getStringArray(R.array.gp_values) 65 | .indexOf(GlobalValues.gpOption) 66 | ] 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/tools/VolunteerUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.tools 2 | 3 | import com.qhy040404.libraryonetap.utils.extensions.toJson 4 | 5 | object VolunteerUtils { 6 | fun createVolunteerPostData(name: String, id: String) = 7 | VolunteerDataClass(name, id).toJson()!! 8 | 9 | data class VolunteerDataClass( 10 | val name: String? = null, 11 | val stu_id: String? = null 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/web/CookieJarImpl.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.web 2 | 3 | import okhttp3.Cookie 4 | import okhttp3.CookieJar 5 | import okhttp3.HttpUrl 6 | 7 | object CookieJarImpl : CookieJar { 8 | private val cookieStore = hashMapOf>() 9 | 10 | override fun saveFromResponse(url: HttpUrl, cookies: List) { 11 | if (cookieStore[url.host] == null) cookieStore[url.host] = hashSetOf() 12 | cookieStore[url.host]?.replaceIfExists(cookies) 13 | } 14 | 15 | override fun loadForRequest(url: HttpUrl): List { 16 | return cookieStore[url.host]?.toList() ?: emptyList() 17 | } 18 | 19 | private fun MutableSet.replaceIfExists(newData: Collection) { 20 | this.removeAll(newData.toSet()) 21 | this.addAll(newData) 22 | } 23 | 24 | fun reset() { 25 | cookieStore.clear() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/utils/web/WebVPNUtils.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.utils.web 2 | 3 | import com.qhy040404.libraryonetap.constant.GlobalValues 4 | import com.qhy040404.libraryonetap.constant.URLManager 5 | import com.qhy040404.libraryonetap.utils.AppUtils 6 | import com.qhy040404.libraryonetap.utils.encrypt.AesEncryptUtils 7 | 8 | object WebVPNUtils { 9 | fun init() { 10 | if (!AppUtils.checkData(GlobalValues.id, GlobalValues.passwd)) { 11 | return 12 | } 13 | val apiPostData = 14 | "schoolcode=dlut&username=${GlobalValues.id}&password=${GlobalValues.passwd}&ssokey=" 15 | Requests.post(URLManager.WEBVPN_INIT_URL, apiPostData, GlobalValues.ctVCard) 16 | } 17 | 18 | fun encryptUrl(url: String): String { 19 | val protocol: String 20 | var port = "" 21 | 22 | var mUrl = if (url.startsWith("http://")) { 23 | protocol = "http" 24 | url.removePrefix("http://") 25 | } else if (url.startsWith("https://")) { 26 | protocol = "https" 27 | url.removePrefix("https://") 28 | } else { 29 | throw IllegalArgumentException("Illegal url") 30 | } 31 | 32 | var v6 = "" 33 | Regex("\\[[\\da-fA-F:]+?]").find(mUrl).let { 34 | if (it != null) { 35 | v6 = it.value 36 | mUrl = mUrl.substring(v6.length) 37 | } 38 | } 39 | val segments = mUrl.substringBefore('?').split(':') 40 | if (segments.size > 1) { 41 | port = segments[1].substringBefore('/') 42 | mUrl = mUrl.substring(0, segments[0].length) + 43 | mUrl.substring(segments[0].length + port.length + 1) 44 | } 45 | 46 | val i = mUrl.indexOf('/') 47 | if (i == -1) { 48 | if (v6.isNotEmpty()) { 49 | mUrl = v6 50 | } 51 | mUrl = AesEncryptUtils.vpnEncrypt(mUrl, "wrdvpnisthebest!", "wrdvpnisthebest!") 52 | } else { 53 | var host = mUrl.substring(0, i) 54 | val path = mUrl.substring(i) 55 | if (v6.isNotEmpty()) { 56 | host = v6 57 | } 58 | mUrl = AesEncryptUtils.vpnEncrypt(host, "wrdvpnisthebest!", "wrdvpnisthebest!") + path 59 | } 60 | return if (port.isNotEmpty()) { 61 | "${URLManager.WEBVPN_INSTITUTION_URL}/$protocol-$port/$mUrl" 62 | } else { 63 | "${URLManager.WEBVPN_INSTITUTION_URL}/$protocol/$mUrl" 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/view/PasswordPreference.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.view 2 | 3 | import android.content.Context 4 | import android.graphics.Typeface 5 | import android.text.InputType 6 | import android.text.method.PasswordTransformationMethod 7 | import android.util.AttributeSet 8 | import androidx.annotation.AttrRes 9 | import androidx.annotation.StyleRes 10 | import com.qhy040404.libraryonetap.utils.encrypt.DesEncryptUtils 11 | import com.takisoft.preferencex.EditTextPreference 12 | 13 | @Suppress("unused") 14 | class PasswordPreference : EditTextPreference { 15 | constructor(context: Context) : super(context) 16 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 17 | constructor(context: Context, attrs: AttributeSet?, @AttrRes defStyleAttr: Int) : super( 18 | context, 19 | attrs, 20 | defStyleAttr 21 | ) 22 | 23 | constructor( 24 | context: Context, 25 | attrs: AttributeSet?, 26 | @AttrRes defStyleAttr: Int, 27 | @StyleRes defStyleRes: Int 28 | ) : super(context, attrs, defStyleAttr, defStyleRes) 29 | 30 | init { 31 | if (summaryProvider is SimpleSummaryProvider) { 32 | summaryProvider = PasswordSummaryProvider 33 | } 34 | setOnBindEditTextListener { editText -> 35 | editText.inputType = 36 | InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD 37 | editText.typeface = Typeface.DEFAULT 38 | text?.let { editText.setSelection(it.length) } 39 | } 40 | } 41 | 42 | override fun setText(text: String?) { 43 | return if (text?.length!! > 16) { 44 | super.setText(text) 45 | } else { 46 | super.setText(DesEncryptUtils.strEnc(text, "q", "h", "y")) 47 | } 48 | } 49 | 50 | override fun getText(): String? { 51 | val currentText = super.getText() 52 | return if (currentText == null) { 53 | null 54 | } else { 55 | DesEncryptUtils.strDec(currentText, "q", "h", "y") 56 | } 57 | } 58 | 59 | object PasswordSummaryProvider : SummaryProvider { 60 | override fun provideSummary(preference: EditTextPreference): CharSequence? { 61 | val text = preference.text 62 | return if (!text.isNullOrEmpty()) { 63 | PasswordTransformationMethod.getInstance().getTransformation(text, null) 64 | } else { 65 | SimpleSummaryProvider.getInstance().provideSummary(preference) 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/qhy040404/libraryonetap/view/ToastView.kt: -------------------------------------------------------------------------------- 1 | package com.qhy040404.libraryonetap.view 2 | 3 | import android.content.Context 4 | import android.graphics.Color 5 | import android.view.Gravity 6 | import android.view.ViewGroup 7 | import androidx.appcompat.widget.AppCompatImageView 8 | import androidx.appcompat.widget.AppCompatTextView 9 | import com.absinthe.libraries.utils.view.AViewGroup 10 | import com.qhy040404.libraryonetap.R 11 | 12 | /** 13 | * From Absinthe 14 | * @author Absinthe, qhy040404 15 | */ 16 | class ToastView(context: Context) : AViewGroup(context) { 17 | val message = AppCompatTextView(context).apply { 18 | layoutParams = 19 | LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) 20 | maxWidth = 300.dp 21 | gravity = Gravity.CENTER 22 | setTextAppearance(android.R.style.TextAppearance_Material_Body2) 23 | setTextColor(Color.BLACK) 24 | val padding = 12.dp 25 | setPadding(padding, padding, padding, padding) 26 | setBackgroundResource(R.drawable.bg_toast) 27 | addView(this) 28 | } 29 | 30 | private val icon = AppCompatImageView(context).apply { 31 | layoutParams = LayoutParams(24.dp, 24.dp) 32 | setImageResource(R.drawable.pic_splash) 33 | addView(this) 34 | } 35 | 36 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 37 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 38 | message.autoMeasure() 39 | icon.autoMeasure() 40 | setMeasuredDimension( 41 | message.measuredWidth, 42 | message.measuredHeight + icon.measuredHeight / 2 43 | ) 44 | } 45 | 46 | override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { 47 | icon.let { it.layout(it.toHorizontalCenter(this), 0) } 48 | message.layout(0, icon.measuredHeight / 2) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/bg_fs_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/bg_toast.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/pic_splash.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qhy040404/Library-One-Tap-Android/63799d543e2156c422c5f4c6c8f4bb14cdfb3f25/app/src/main/res/drawable-xxxhdpi/pic_splash.webp -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_fs_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_about.xml: -------------------------------------------------------------------------------- 1 | 7 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_about_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 72 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cache.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 17 | 22 | 27 | 32 | 37 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_card.xml: -------------------------------------------------------------------------------- 1 | 7 | 14 | 21 | 28 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_changelog.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_color_palette.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 | 39 | 42 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dark_mode.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_elec.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_exams.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_gpa.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_grades.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_issue.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 17 | 22 | 27 | 32 | 37 | 42 | 47 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_language.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 16 | 21 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_lessons.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_material.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_multi.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 17 | 22 | 27 | 32 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_passwd.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 17 | 22 | 27 | 32 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sc_exams.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sc_qr.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 19 | 22 | 25 | 28 | 31 | 34 | 37 | 40 | 43 | 46 | 49 | 52 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sc_tool.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sc_vcard.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 17 | 24 | 31 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_single.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_tools.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 17 | 22 | 27 | 32 | 37 | 42 | 47 | 52 | 57 | 62 | 67 | 72 | 77 | 82 | 83 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_update.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_userid.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_volunteer.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/simplepage_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/white_back_btn.xml: -------------------------------------------------------------------------------- 1 | 16 | 22 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 22 | 23 | 29 | 30 | 31 | 40 | 41 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_vcard.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 20 | 21 | 27 | 28 | 29 | 39 | 40 | 54 | 55 |