├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── ic_launcher-playstore.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.png
│ │ │ │ └── ic_launcher_round.png
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── layout
│ │ │ │ ├── activity_debug.xml
│ │ │ │ ├── fragment_project_downloads.xml
│ │ │ │ ├── fragment_project_description.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ ├── fragment_list.xml
│ │ │ │ ├── download_item.xml
│ │ │ │ ├── fragment_settings_container.xml
│ │ │ │ ├── drawer_header.xml
│ │ │ │ ├── list_item.xml
│ │ │ │ ├── list_downloads.xml
│ │ │ │ ├── fragment_home.xml
│ │ │ │ ├── fragment_project_view.xml
│ │ │ │ ├── activity_auth.xml
│ │ │ │ ├── fragment_user.xml
│ │ │ │ └── fragment_project_info.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ ├── data_extraction_rules.xml
│ │ │ │ └── root_preferences.xml
│ │ │ ├── drawable
│ │ │ │ ├── ic_texture.xml
│ │ │ │ ├── ic_edit.xml
│ │ │ │ ├── ic_arrow_back.xml
│ │ │ │ ├── ic_search.xml
│ │ │ │ ├── ic_menu.xml
│ │ │ │ ├── ic_download.xml
│ │ │ │ ├── ic_description.xml
│ │ │ │ ├── ic_inventory_2.xml
│ │ │ │ ├── ic_info.xml
│ │ │ │ ├── ic_build.xml
│ │ │ │ ├── ic_person.xml
│ │ │ │ ├── ic_update.xml
│ │ │ │ ├── ic_launcher_foreground.xml
│ │ │ │ ├── ic_share.xml
│ │ │ │ ├── ic_extension.xml
│ │ │ │ └── ic_settings.xml
│ │ │ ├── menu
│ │ │ │ ├── main_menu.xml
│ │ │ │ ├── toolbar_menu_user.xml
│ │ │ │ ├── project_view_menu.xml
│ │ │ │ └── toolbar_menu.xml
│ │ │ ├── values
│ │ │ │ ├── arrays.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── strings.xml
│ │ │ │ └── themes.xml
│ │ │ ├── values-night
│ │ │ │ └── themes.xml
│ │ │ └── raw
│ │ │ │ └── licenses.txt
│ │ ├── java
│ │ │ └── me
│ │ │ │ └── theclashfruit
│ │ │ │ └── rithle
│ │ │ │ ├── models
│ │ │ │ ├── ModrinthModMessageModel.kt
│ │ │ │ ├── ModrinthLicenseModel.kt
│ │ │ │ ├── ModrinthDonationUrlsModel.kt
│ │ │ │ ├── GitHubAccessTokenModel.kt
│ │ │ │ ├── ModrinthGalleryModel.kt
│ │ │ │ ├── ModrinthSearchModel.kt
│ │ │ │ ├── ModrinthSearchHitsModel.kt
│ │ │ │ └── ModrinthProjectModel.kt
│ │ │ │ ├── classes
│ │ │ │ ├── ProxySchemeHandler.kt
│ │ │ │ ├── MrApiUrlUtil.kt
│ │ │ │ ├── ListDiffCallback.kt
│ │ │ │ ├── RithleSingleton.kt
│ │ │ │ └── FilterBuilder.kt
│ │ │ │ ├── RithleApplication.kt
│ │ │ │ ├── fragments
│ │ │ │ ├── SettingsFragment.kt
│ │ │ │ ├── SettingsContainerFragment.kt
│ │ │ │ ├── ProjectDescriptionFragment.kt
│ │ │ │ ├── ProjectDownloadsFragment.kt
│ │ │ │ ├── ListFragment.kt
│ │ │ │ ├── ProjectViewFragment.kt
│ │ │ │ ├── UserFragment.kt
│ │ │ │ ├── HomeFragment.kt
│ │ │ │ └── ProjectInfoFragment.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── adapters
│ │ │ │ ├── ModListAdapter.kt
│ │ │ │ └── DownloadsAdapter.kt
│ │ │ │ ├── services
│ │ │ │ └── NotificationService.kt
│ │ │ │ └── AuthActivity.kt
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── me
│ │ │ └── theclashfruit
│ │ │ └── rithle
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── me
│ │ └── theclashfruit
│ │ └── rithle
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── .idea
├── .gitignore
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── compiler.xml
├── vcs.xml
├── discord.xml
├── gradle.xml
└── misc.xml
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .github
├── ISSUE_TEMPLATE
│ ├── other.md
│ ├── feature_request.md
│ └── bug_report.md
├── FUNDING.yml
└── workflows
│ └── android.yml
├── .gitignore
├── settings.gradle
├── CONTRIBUTING.md
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/other.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Other
3 | about: Anything other.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TheClashFruit/Rithle/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/discord.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Nov 23 13:54:36 CET 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/models/ModrinthModMessageModel.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ModrinthModMessageModel(
7 | var message : String? = null,
8 | var body : String? = null
9 | )
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/models/ModrinthLicenseModel.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ModrinthLicenseModel (
7 | var id : String? = null,
8 | var name : String? = null,
9 | var url : String? = null
10 | )
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/models/ModrinthDonationUrlsModel.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ModrinthDonationUrlsModel (
7 | var id : String? = null,
8 | var platform : String? = null,
9 | var url : String? = null
10 | )
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/models/GitHubAccessTokenModel.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class GitHubAccessTokenModel (
7 | var access_token : String? = null,
8 | var token_type : String? = null,
9 | var scope : String? = null
10 | )
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Rithle"
16 | include ':app'
17 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/models/ModrinthGalleryModel.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ModrinthGalleryModel (
7 | var url : String? = null,
8 | var featured : Boolean? = null,
9 | var title : String? = null,
10 | var description : String? = null,
11 | var created : String? = null
12 | )
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/classes/ProxySchemeHandler.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.classes
2 |
3 | import android.net.Uri
4 | import io.noties.markwon.image.ImageItem
5 | import io.noties.markwon.image.SchemeHandler
6 |
7 | abstract class ProxySchemeHandler : SchemeHandler() {
8 | abstract override fun handle(raw: String, uri: Uri): ImageItem
9 | abstract override fun supportedSchemes(): Collection
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/test/java/me/theclashfruit/rithle/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_debug.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_project_downloads.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/classes/MrApiUrlUtil.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.classes
2 |
3 | import me.theclashfruit.rithle.BuildConfig
4 |
5 | class MrApiUrlUtil {
6 | private val stableUrl = "https://api.modrinth.com"
7 | private val stagingUrl = "https://staging-api.modrinth.com"
8 |
9 | fun getApiUrl(): String {
10 | return if (BuildConfig.DEBUG) {
11 | stagingUrl
12 | } else {
13 | stableUrl
14 | }
15 | }
16 |
17 | fun getIsDebugMode(): Boolean {
18 | return BuildConfig.DEBUG
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/models/ModrinthSearchModel.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.models
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ModrinthSearchModel(
8 | @SerializedName("hits" ) var hits : ArrayList = arrayListOf(),
9 | @SerializedName("offset" ) var offset : Int? = null,
10 | @SerializedName("limit" ) var limit : Int? = null,
11 | @SerializedName("total_hits" ) var totalHits : Int? = null
12 | )
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_project_description.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_texture.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_edit.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/main_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
12 |
16 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/toolbar_menu_user.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | - EUR
4 | - GBP
5 | - AED
6 | - AFN
7 | - AMD
8 | - ALL
9 | - ANG
10 | - HUF
11 |
12 |
13 |
14 |
15 |
16 | - 0.942
17 | - 0.830
18 | - 3.6726
19 | - 85.300
20 | - 379.670
21 | - 106.875
22 | - 1.790
23 | - 376.301
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_back.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/me/theclashfruit/rithle/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("me.theclashfruit.rithle", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Smartphone (please complete the following information):**
27 | - Device: [e.g. Samsung Galaxy A52s 5G]
28 | - OS: [e.g. Android 12]
29 | - Version [e.g. 1.3.4-stable]
30 |
31 | **Additional context**
32 | Add any other context about the problem here.
33 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: [ TheClashFruit ] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: TheClashFruit # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/project_view_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
11 |
15 |
19 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #1bd96a
4 | #1bd96a
5 |
6 | #00af5c
7 | #00af5c
8 |
9 | #4f9cff
10 | #4f9cff
11 |
12 | #e5e7eb
13 | #16181C
14 |
15 | #ffffff
16 | #26292f
17 |
18 | #FF000000
19 | #99000000
20 | #FFFFFFFF
21 | #99FFFFFF
22 |
23 | #16181C
24 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/classes/ListDiffCallback.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.classes
2 |
3 | import androidx.recyclerview.widget.DiffUtil
4 | import me.theclashfruit.rithle.models.ModrinthSearchHitsModel
5 |
6 | class ListDiffCallback constructor(private val oldList: ArrayList?, private val newList: ArrayList?) : DiffUtil.Callback() {
7 | override fun getOldListSize(): Int {
8 | return oldList!!.size
9 | }
10 |
11 | override fun getNewListSize(): Int {
12 | return newList!!.size
13 | }
14 |
15 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
16 | return oldList!![oldItemPosition].project_id == newList!![newItemPosition].project_id
17 | }
18 |
19 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
20 | return oldList!![oldItemPosition].title.equals(newList!![newItemPosition].title)
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/toolbar_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
11 |
18 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_download.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/android.yml:
--------------------------------------------------------------------------------
1 | name: Android CI
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main", "*-dev" ]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: set up JDK 11
18 | uses: actions/setup-java@v3
19 | with:
20 | java-version: '11'
21 | distribution: 'temurin'
22 | cache: gradle
23 | - name: Grant execute permission for Gradlew
24 | run: chmod +x gradlew
25 | - name: Add Oauth tokens to local.properties
26 | run: |
27 | echo ghclient=\"$GHO_CLIENT\" >> local.properties
28 | echo ghsecret=\"$GHO_SECRET\" >> local.properties
29 | env:
30 | GHO_SECRET: ${{ secrets.GHO_SECRET }}
31 | GHO_CLIENT: ${{ secrets.GHO_CLIENT }}
32 | - name: Build with Gradle
33 | run: ./gradlew assembleRelease
34 | - name: Upload APK artifacts
35 | uses: actions/upload-artifact@v3
36 | with:
37 | name: APK artifacts
38 | path: app/build/outputs/apk/release
39 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_description.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_inventory_2.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | ## Branches
4 |
5 | - `main` Stable release of app, do not commit your changes here
6 | - `dev` Development branch, commit your changes here
7 |
8 | ## Commit Messages
9 |
10 | Please do try to follow the committing style if you wanted to contribute, it makes the repository much more clean and consistent.
11 | - Do not squash commits
12 | - Explain what the commit does or what did you do in the commit message with less than 50 characters
13 | - Use the commit description if you can't fit what you did in the commit message
14 | - Use one of these prefixes in your commit messages:
15 | - `fix` When you fixed a bug or maybe a flaw within the codebase
16 | - `feat` When you added something within the codebase (can be anything)
17 | - `tweak` When you do a little tweak in the codebase, like a tiny UI change
18 | - `chore` When you do fix something, but it doesn't affect the app in terms of functionality
19 | - `refactor` When you refactored the code, like cleaning up the code
20 |
21 | ## Pull Requests
22 |
23 | - Please be descriptive with you pull request
24 | - Please do not ping anyone in your pull request, it will not make the reviewing process faster
25 | - Please do not reopen your pull request without any changes if it has been denied
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_info.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/download_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
16 |
17 |
29 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_build.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_person.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_update.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/RithleApplication.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle
2 |
3 | import android.app.Application
4 | import android.os.Process
5 | import android.util.Log
6 | import java.io.BufferedWriter
7 | import java.io.File
8 | import java.io.FileWriter
9 | import java.io.IOException
10 | import java.text.SimpleDateFormat
11 | import java.util.*
12 | import kotlin.system.exitProcess
13 |
14 |
15 | class RithleApplication : Application() {
16 | override fun onCreate() {
17 | super.onCreate()
18 |
19 | initializeExceptionHandler()
20 |
21 | val logDelete = File(applicationContext.dataDir.toString() + "/files/latest.log")
22 | if (logDelete.exists()) {
23 | val newLogFile = File(applicationContext.dataDir.toString() + "/files/" + SimpleDateFormat("yyyy-MM-dd_HH.mm.ss").format(Date()) + ".log")
24 |
25 | newLogFile.createNewFile()
26 | newLogFile.writeText(logDelete.readText())
27 |
28 | logDelete.delete()
29 | }
30 | }
31 |
32 | private fun initializeExceptionHandler() {
33 | Thread.setDefaultUncaughtExceptionHandler { _, ex ->
34 | Log.e("RithleApplication", ex.stackTraceToString())
35 |
36 | val newLogFile = File(applicationContext.dataDir.toString() + "/files/latest.log")
37 |
38 | newLogFile.writeText("------ BEGINNING OF CRASH\n" + ex.stackTraceToString() + "\n------------------")
39 |
40 | Process.killProcess(Process.myPid())
41 | exitProcess(1)
42 | }
43 | }
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/classes/RithleSingleton.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.classes
2 |
3 | import android.content.Context
4 | import android.graphics.Bitmap
5 | import android.util.LruCache
6 | import com.android.volley.Request
7 | import com.android.volley.RequestQueue
8 | import com.android.volley.toolbox.ImageLoader
9 | import com.android.volley.toolbox.Volley
10 |
11 | class RithleSingleton constructor(context: Context) {
12 | companion object {
13 | @Volatile
14 | private var INSTANCE: RithleSingleton? = null
15 | fun getInstance(context: Context) =
16 | INSTANCE ?: synchronized(this) {
17 | INSTANCE ?: RithleSingleton(context).also {
18 | INSTANCE = it
19 | }
20 | }
21 | }
22 |
23 | val imageLoader: ImageLoader by lazy {
24 | ImageLoader(requestQueue,
25 | object : ImageLoader.ImageCache {
26 | private val cache = LruCache(1024)
27 | override fun getBitmap(url: String): Bitmap? {
28 | return cache.get(url)
29 | }
30 | override fun putBitmap(url: String, bitmap: Bitmap) {
31 | cache.put(url, bitmap)
32 | }
33 | })
34 | }
35 |
36 | private val requestQueue: RequestQueue by lazy {
37 | Volley.newRequestQueue(context.applicationContext)
38 | }
39 |
40 | fun addToRequestQueue(req: Request) {
41 | requestQueue.add(req)
42 | }
43 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/fragments/SettingsFragment.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.fragments
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.appcompat.app.AppCompatDelegate
6 | import androidx.preference.CheckBoxPreference
7 | import androidx.preference.Preference
8 | import androidx.preference.PreferenceFragmentCompat
9 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
10 | import me.theclashfruit.rithle.R
11 |
12 | class SettingsFragment : PreferenceFragmentCompat() {
13 |
14 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
15 | setPreferencesFromResource(R.xml.root_preferences, rootKey)
16 |
17 | val licensePref = findPreference("licensesItem")
18 | val darkModePRef = findPreference("darkMode")
19 |
20 | licensePref!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
21 | R.raw.licenses.toString()
22 |
23 | MaterialAlertDialogBuilder(requireContext())
24 | .setTitle("Licenses")
25 | .setMessage(requireContext().resources.openRawResource(R.raw.licenses).readBytes().decodeToString())
26 | .setPositiveButton("Ok") { _, _ -> }
27 | .show()
28 |
29 | return@OnPreferenceClickListener true
30 | }
31 |
32 | darkModePRef!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { p, v ->
33 | return@OnPreferenceChangeListener true
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/classes/FilterBuilder.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.classes
2 |
3 | class FilterBuilder {
4 | private val filterData: ArrayList> = arrayListOf(arrayListOf())
5 |
6 | fun addFilterItem(filter: String): FilterBuilder {
7 | filterData[0].add(
8 | filter
9 | .replace("'", "%27")
10 | .replace(":", "%3A")
11 | )
12 |
13 | return this
14 | }
15 |
16 | fun setProjectType(projectType: String): FilterBuilder {
17 | when(projectType) {
18 | "mod" -> filterData.add(arrayListOf("project_type:$projectType"))
19 | "modpack" -> filterData.add(arrayListOf("project_type:$projectType"))
20 | "resourcepack" -> filterData.add(arrayListOf("project_type:$projectType"))
21 | else -> throw NoSuchFieldError("Invalid Project Type!")
22 | }
23 |
24 | return this
25 | }
26 |
27 | fun build(): String {
28 | val fLick: ArrayList = arrayListOf()
29 |
30 | if(filterData[0].isEmpty())
31 | filterData.removeAt(0)
32 |
33 | filterData.forEach {
34 | fLick.add(
35 | it.joinToString(
36 | separator = "%22%2C%22",
37 | prefix = "%5B%22",
38 | postfix = "%22%5D"
39 | )
40 | )
41 | }
42 |
43 | return fLick.joinToString(
44 | separator = "%2C",
45 | prefix = "%5B",
46 | postfix = "%5D"
47 | )
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_settings_container.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
26 |
27 |
28 |
29 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/fragments/SettingsContainerFragment.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.fragments
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Bundle
5 | import androidx.fragment.app.Fragment
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.FrameLayout
10 | import com.google.android.material.appbar.MaterialToolbar
11 | import me.theclashfruit.rithle.R
12 |
13 | class SettingsContainerFragment : Fragment() {
14 | override fun onCreate(savedInstanceState: Bundle?) {
15 | super.onCreate(savedInstanceState)
16 | arguments?.let {}
17 | }
18 |
19 | override fun onCreateView(
20 | inflater: LayoutInflater, container: ViewGroup?,
21 | savedInstanceState: Bundle?
22 | ): View? {
23 | val rootView = inflater.inflate(R.layout.fragment_settings_container, container, false)
24 |
25 | val toolBar: MaterialToolbar = rootView.findViewById(R.id.toolbar)
26 |
27 | toolBar.setNavigationOnClickListener {
28 | parentFragmentManager.popBackStack()
29 | }
30 |
31 | val fragmentTransaction = parentFragmentManager.beginTransaction()
32 | val settingsFragment = SettingsFragment()
33 |
34 | fragmentTransaction
35 | .replace(R.id.settingsContainer, settingsFragment)
36 | .commit()
37 |
38 | return rootView
39 | }
40 |
41 | companion object {
42 | @JvmStatic
43 | fun newInstance() =
44 | SettingsContainerFragment().apply {
45 | arguments = Bundle().apply {}
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/fragments/ProjectDescriptionFragment.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.fragments
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import kotlinx.serialization.decodeFromString
9 | import kotlinx.serialization.json.Json
10 | import me.theclashfruit.rithle.R
11 | import me.theclashfruit.rithle.models.ModrinthProjectModel
12 | import me.theclashfruit.rithle.models.ModrinthSearchModel
13 | import org.json.JSONArray
14 | import org.json.JSONObject
15 |
16 | class ProjectDescriptionFragment : Fragment() {
17 | private var projectData: ModrinthProjectModel? = null
18 |
19 | override fun onCreate(savedInstanceState: Bundle?) {
20 | super.onCreate(savedInstanceState)
21 |
22 | val format = Json { ignoreUnknownKeys = true }
23 |
24 | arguments?.let {
25 | projectData = format.decodeFromString(it.getString("projectData")!!)
26 | }
27 | }
28 |
29 | override fun onCreateView(
30 | inflater: LayoutInflater, container: ViewGroup?,
31 | savedInstanceState: Bundle?
32 | ): View? {
33 | // Inflate the layout for this fragment
34 | return inflater.inflate(R.layout.fragment_project_description, container, false)
35 | }
36 |
37 | companion object {
38 | @JvmStatic
39 | fun newInstance(projectDataString: String) =
40 | ProjectDescriptionFragment().apply {
41 | arguments = Bundle().apply {
42 | putString("projectData", projectDataString)
43 | }
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_share.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_extension.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 | import androidx.appcompat.app.AppCompatActivity
7 | import android.os.Bundle
8 | import androidx.browser.customtabs.CustomTabsIntent
9 | import androidx.core.content.ContextCompat
10 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
11 | import me.theclashfruit.rithle.fragments.HomeFragment
12 | import me.theclashfruit.rithle.services.NotificationService
13 |
14 | class MainActivity : AppCompatActivity() {
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 | setContentView(R.layout.activity_main)
19 |
20 | val mainFragmentTransaction = supportFragmentManager.beginTransaction()
21 | val homeFragment = HomeFragment.newInstance()
22 |
23 | val sharedPref = getSharedPreferences("me.theclashfruit.rithle_preferences", Context.MODE_PRIVATE)
24 | val authToken = sharedPref!!.getString("authToken", "")
25 |
26 | if(authToken != "")
27 | ContextCompat.startForegroundService(this, Intent(this, NotificationService::class.java))
28 |
29 | mainFragmentTransaction
30 | .replace(R.id.parentFragmentContainer, homeFragment)
31 | .commit()
32 |
33 | /*
34 | MaterialAlertDialogBuilder(this)
35 | .setTitle("⚠️ Warning!")
36 | .setMessage("This is an alpha build, it is not intended for regular use, report bugs on GitHub.")
37 | .setPositiveButton("Ok") { dialog, which ->
38 |
39 | }
40 | .show()
41 | */
42 |
43 | // https://github.com/login/oauth/authorize?client_id=2f7fbf1e6e196b0d2069
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/drawer_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
27 |
28 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_settings.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
32 |
33 |
36 |
37 |
42 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
14 |
15 |
20 |
21 |
22 |
23 |
28 |
29 |
37 |
38 |
45 |
46 |
53 |
54 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/models/ModrinthSearchHitsModel.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.models
2 |
3 | import com.google.gson.annotations.SerializedName
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class ModrinthSearchHitsModel(
9 | @SerializedName("project_id" ) var project_id : String? = null,
10 | @SerializedName("project_type" ) var project_type : String? = null,
11 | @SerializedName("slug" ) var slug : String? = null,
12 | @SerializedName("author" ) var author : String? = null,
13 | @SerializedName("title" ) var title : String? = null,
14 | @SerializedName("description" ) var description : String? = null,
15 | @SerializedName("categories" ) var categories : ArrayList = arrayListOf(),
16 | @SerializedName("display_categories" ) var display_categories : ArrayList = arrayListOf(),
17 | @SerializedName("versions" ) var versions : ArrayList = arrayListOf(),
18 | @SerializedName("downloads" ) var downloads : Int? = null,
19 | @SerializedName("follows" ) var follows : Int? = null,
20 | @SerializedName("icon_url" ) var icon_url : String? = null,
21 | @SerializedName("date_created" ) var date_created : String? = null,
22 | @SerializedName("date_modified" ) var date_modified : String? = null,
23 | @SerializedName("latest_version" ) var latest_version : String? = null,
24 | @SerializedName("license" ) var license : String? = null,
25 | @SerializedName("client_side" ) var client_side : String? = null,
26 | @SerializedName("server_side" ) var server_side : String? = null,
27 | @SerializedName("gallery" ) var gallery : ArrayList = arrayListOf()
28 | )
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_downloads.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
21 |
22 |
34 |
35 |
47 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/root_preferences.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
14 |
15 |
16 |
17 |
23 |
28 |
29 |
30 |
31 |
36 |
41 |
42 |
43 |
46 |
49 |
50 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/models/ModrinthProjectModel.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.models
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class ModrinthProjectModel (
7 | var id : String? = null,
8 | var slug : String? = null,
9 | var project_type : String? = null,
10 | var team : String? = null,
11 | var title : String? = null,
12 | var description : String? = null,
13 | var body : String? = null,
14 | var body_url : String? = null,
15 | var published : String? = null,
16 | var updated : String? = null,
17 | var approved : String? = null,
18 | var status : String? = null,
19 | var moderator_message : ModrinthModMessageModel? = ModrinthModMessageModel(),
20 | var license : ModrinthLicenseModel? = ModrinthLicenseModel(),
21 | var client_side : String? = null,
22 | var server_side : String? = null,
23 | var downloads : Int? = null,
24 | var followers : Int? = null,
25 | var categories : ArrayList = arrayListOf(),
26 | var additional_categories : ArrayList = arrayListOf(),
27 | var versions : ArrayList = arrayListOf(),
28 | var icon_url : String? = null,
29 | var issues_url : String? = null,
30 | var source_url : String? = null,
31 | var wiki_url : String? = null,
32 | var discord_url : String? = null,
33 | var donation_urls : ArrayList = arrayListOf(),
34 | var gallery : ArrayList = arrayListOf()
35 | )
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_home.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
12 |
13 |
23 |
24 |
25 |
26 |
30 |
31 |
39 |
40 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/licenses.txt:
--------------------------------------------------------------------------------
1 | ========== Volley ==
2 |
3 | Copyright 2022 Google
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | Repository: https://github.com/google/volley
18 | Full License: https://github.com/google/volley/blob/master/LICENSE
19 |
20 | ====================
21 |
22 | ========= Markwon ==
23 |
24 | Copyright 2019 Dimitry Ivanov (legal@noties.io)
25 |
26 | Licensed under the Apache License, Version 2.0 (the "License");
27 | you may not use this file except in compliance with the License.
28 | You may obtain a copy of the License at
29 |
30 | http://www.apache.org/licenses/LICENSE-2.0
31 |
32 | Unless required by applicable law or agreed to in writing, software
33 | distributed under the License is distributed on an "AS IS" BASIS,
34 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
35 | See the License for the specific language governing permissions and
36 | limitations under the License.
37 |
38 | Repository: https://github.com/noties/Markwon
39 | Full License: https://github.com/noties/Markwon/blob/master/LICENSE
40 |
41 | ====================
42 |
43 | ========== Rithle ==
44 |
45 | Rithle, Android app for Modrinth written in Kotlin.
46 | Copyright (C) 2022 TheClashFruit
47 |
48 | This program is free software: you can redistribute it and/or modify
49 | it under the terms of the GNU General Public License as published by
50 | the Free Software Foundation, either version 3 of the License, or
51 | (at your option) any later version.
52 |
53 | This program is distributed in the hope that it will be useful,
54 | but WITHOUT ANY WARRANTY; without even the implied warranty of
55 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
56 | GNU General Public License for more details.
57 |
58 | You should have received a copy of the GNU General Public License
59 | along with this program. If not, see .
60 |
61 | Repository: https://github.com/TheClashFruit/Rithle
62 | Full License: https://github.com/TheClashFruit/Rithle/blob/master/LICENSE
63 |
64 | ====================
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_project_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
26 |
27 |
28 |
29 |
33 |
34 |
42 |
43 |
44 |
45 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
24 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Rithle
3 |
4 | Login
5 | Logout
6 | Profile
7 | Search
8 | Mods
9 | Plugins
10 | Resource Packs
11 | Modpacks
12 | Info
13 | Description
14 | Changelog
15 | Downloads
16 | Settings
17 | Report
18 |
19 | Theme
20 | Advanced
21 | Experimental
22 | About
23 |
24 | Material You
25 | Dark Mode
26 | Force the app to use dark mode even if the device is in light mode.
27 | GitHub Token
28 | Your GitHub token used to authenticate requests to Modrinth.
29 | Debug Mode
30 | Adds additional info to the logs.
31 | Proxy Images
32 | Proxies images in project descriptions.
33 | Modpack Creator
34 | Create modpacks with a packwiz compatible gui based editor.
35 | Licenses
36 | Copyright
37 | Copyright © 2022-2023 TheClashFruit
38 |
39 | There was an unexpected error while trying to process the authentication with GitHub, please enter a GitHub token in the settings.
40 | There was an error while trying to complete the authentication with Modrinth, please enter a GitHub token in the settings:\n%s
41 |
42 |
43 | - %s Download
44 | - %s Downloads
45 |
46 |
47 | - %s Follower
48 | - %s Followers
49 |
50 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'org.jetbrains.kotlin.plugin.serialization'
5 | }
6 |
7 | android {
8 | compileSdk 33
9 |
10 | defaultConfig {
11 | applicationId "me.theclashfruit.rithle"
12 | minSdk 27
13 | targetSdk 33
14 | versionCode 3
15 | versionName "0.3.0-beta"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 |
19 | Properties properties = new Properties()
20 | properties.load(project.rootProject.file("local.properties").newDataInputStream())
21 |
22 | buildConfigField "String", "GH_SECRET", "${properties.getProperty("ghsecret")}"
23 | buildConfigField "String", "GH_CLIENT", "${properties.getProperty("ghclient")}"
24 | }
25 |
26 | buildTypes {
27 | release {
28 | minifyEnabled false
29 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
30 | }
31 | }
32 |
33 | compileOptions {
34 | sourceCompatibility JavaVersion.VERSION_1_8
35 | targetCompatibility JavaVersion.VERSION_1_8
36 | }
37 |
38 | kotlinOptions {
39 | jvmTarget = '1.8'
40 | }
41 | }
42 |
43 | configurations {
44 | cleanedAnnotations
45 | implementation.exclude group: 'org.jetbrains' , module:'annotations'
46 | }
47 |
48 | dependencies {
49 | implementation 'androidx.core:core-ktx:1.9.0'
50 | implementation 'androidx.appcompat:appcompat:1.5.1'
51 | implementation 'androidx.lifecycle:lifecycle-viewmodel:2.5.1'
52 | implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
53 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
54 | implementation 'androidx.preference:preference:1.2.0'
55 | implementation 'androidx.browser:browser:1.4.0'
56 |
57 | implementation 'com.google.android.material:material:1.7.0'
58 |
59 | implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1'
60 |
61 | implementation 'io.noties.markwon:core:4.6.2'
62 | implementation 'io.noties.markwon:ext-latex:4.6.2'
63 | implementation 'io.noties.markwon:ext-strikethrough:4.6.2'
64 | implementation 'io.noties.markwon:ext-tables:4.6.2'
65 | implementation 'io.noties.markwon:ext-tasklist:4.6.2'
66 | implementation 'io.noties.markwon:html:4.6.2'
67 | implementation 'io.noties.markwon:image:4.6.2'
68 | implementation 'io.noties.markwon:linkify:4.6.2'
69 | implementation 'io.noties.markwon:syntax-highlight:4.6.2'
70 |
71 | implementation 'com.google.code.gson:gson:2.10'
72 | implementation 'com.android.volley:volley:1.2.1'
73 |
74 | testImplementation 'junit:junit:4.13.2'
75 |
76 | androidTestImplementation 'androidx.test.ext:junit:1.1.4'
77 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
78 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_auth.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
20 |
21 |
30 |
31 |
40 |
41 |
55 |
56 |
65 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_user.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
14 |
15 |
27 |
28 |
29 |
30 |
34 |
35 |
36 |
50 |
51 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/fragments/ProjectDownloadsFragment.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.fragments
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import androidx.fragment.app.Fragment
8 | import android.view.LayoutInflater
9 | import android.view.View
10 | import android.view.ViewGroup
11 | import androidx.recyclerview.widget.LinearLayoutManager
12 | import androidx.recyclerview.widget.RecyclerView
13 | import com.android.volley.toolbox.JsonArrayRequest
14 | import com.android.volley.toolbox.JsonObjectRequest
15 | import kotlinx.serialization.decodeFromString
16 | import me.theclashfruit.rithle.BuildConfig
17 | import me.theclashfruit.rithle.R
18 | import me.theclashfruit.rithle.adapters.DownloadsAdapter
19 | import me.theclashfruit.rithle.classes.MrApiUrlUtil
20 | import me.theclashfruit.rithle.classes.RithleSingleton
21 | import me.theclashfruit.rithle.models.ModrinthProjectModel
22 |
23 | class ProjectDownloadsFragment : Fragment() {
24 | private var modId: String? = null
25 |
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | super.onCreate(savedInstanceState)
28 | arguments?.let {
29 | modId = it.getString("modId")
30 | }
31 | }
32 |
33 | @SuppressLint("MissingInflatedId")
34 | override fun onCreateView(
35 | inflater: LayoutInflater, container: ViewGroup?,
36 | savedInstanceState: Bundle?
37 | ): View? {
38 | val rootView = inflater.inflate(R.layout.fragment_project_downloads, container, false)
39 |
40 | val recyclerView = rootView.findViewById(R.id.recyclerView)
41 |
42 | val jsonObjectRequest = @SuppressLint("SetTextI18n")
43 | object : JsonArrayRequest(
44 | Method.GET, MrApiUrlUtil().getApiUrl() + "/v2/project/${modId}/version", null,
45 | { response ->
46 | val listAdapter = DownloadsAdapter(response, requireContext(), parentFragmentManager)
47 | val layoutManager = LinearLayoutManager(requireContext())
48 |
49 | recyclerView.layoutManager = layoutManager
50 | recyclerView.adapter = listAdapter
51 | },
52 | { error ->
53 | Log.e("webCall", error.toString())
54 | }
55 | ) {
56 | override fun getHeaders(): MutableMap {
57 | val headers = HashMap()
58 | headers["User-Agent"] = "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}) Rithle/${BuildConfig.VERSION_NAME} (github.com/TheClashFruit/Rithle; admin@theclashfruit.me) Volley/1.2.1"
59 | return headers
60 | }
61 | }
62 |
63 | RithleSingleton.getInstance(requireContext()).addToRequestQueue(jsonObjectRequest)
64 |
65 |
66 | return rootView
67 | }
68 |
69 | companion object {
70 | @JvmStatic
71 | fun newInstance(modId: String?) =
72 | ProjectDownloadsFragment().apply {
73 | arguments = Bundle().apply {
74 | putString("modId", modId)
75 | }
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | > [!IMPORTANT]
2 | > Please check out issue [#9](https://github.com/TheClashFruit/Rithle/issues/9) of you are wondering why there hasn't been an update since Febuary of 2023!
3 |
4 | 
5 |
6 |
7 | Rithle
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | Android app for Modrinth written in Kotlin.
22 |
23 |
24 |
25 | Screenshots
26 |
27 |
28 | 
29 |
30 |
31 | Building
32 |
33 |
34 |
40 |
41 |
42 |
43 | Clone the repo with git clone https://github.com/TheClashFruit/Rithle.git
44 | Go into the folder with cd Rithle
45 | Add your github oauth secret and client id to local.properties as ghclient and ghsecret
46 | Build the app with ./gradlew build
47 |
48 |
49 |
50 |
51 | Contributing
52 |
53 |
54 |
55 | Can be found in CONTRIBUTING.md .
56 |
57 |
58 |
59 | Thanks To
60 |
61 |
62 |
63 |
64 | Modrinth - An awesome platform for sharing minecraft mods, plugins and more.
65 | Volley - Network requests.
66 | Markwon - Markdown rendering for the project descriptions.
67 |
68 |
69 |
70 |
71 | License
72 |
73 |
74 |
75 | Rithle, Android app for Modrinth written in Kotlin.
76 | Copyright (C) 2022-2023 TheClashFruit
77 |
78 | This program is free software: you can redistribute it and/or modify
79 | it under the terms of the GNU General Public License as published by
80 | the Free Software Foundation, either version 3 of the License, or
81 | (at your option) any later version.
82 |
83 | This program is distributed in the hope that it will be useful,
84 | but WITHOUT ANY WARRANTY; without even the implied warranty of
85 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
86 | GNU General Public License for more details.
87 |
88 | You should have received a copy of the GNU General Public License
89 | along with this program. If not, see <https://www.gnu.org/licenses/>.
90 |
91 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
29 |
30 |
47 |
48 |
51 |
52 |
57 |
58 |
61 |
62 |
67 |
68 |
71 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/adapters/ModListAdapter.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.adapters
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.util.Log
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.ImageView
10 | import android.widget.TextView
11 | import androidx.fragment.app.FragmentManager
12 | import androidx.recyclerview.widget.RecyclerView
13 | import com.android.volley.VolleyError
14 | import com.android.volley.toolbox.ImageLoader
15 | import me.theclashfruit.rithle.R
16 | import me.theclashfruit.rithle.classes.RithleSingleton
17 | import me.theclashfruit.rithle.fragments.ProjectViewFragment
18 | import me.theclashfruit.rithle.models.ModrinthSearchHitsModel
19 |
20 | class ModListAdapter(private val modList: ArrayList, private val appContext: Context, private val fragmentManager: FragmentManager) : RecyclerView.Adapter() {
21 |
22 | class StreamHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
23 | init {
24 | itemView.setOnClickListener(this)
25 | }
26 |
27 | override fun onClick(itemView: View) {
28 | Log.d("RecyclerView", "CLICK!")
29 | }
30 | }
31 |
32 | override fun onCreateViewHolder(
33 | parent: ViewGroup,
34 | viewType: Int
35 | ): StreamHolder {
36 | val layoutInflater = LayoutInflater.from(parent.context)
37 |
38 | val view: View = layoutInflater.inflate(R.layout.list_item, parent, false)
39 |
40 | return StreamHolder(view)
41 | }
42 |
43 | @SuppressLint("SetTextI18n")
44 | override fun onBindViewHolder(holder: StreamHolder, position: Int) {
45 | holder.itemView.findViewById(R.id.textView).text = modList[position].title
46 | holder.itemView.findViewById(R.id.textView2).text = "by ${modList[position].author}"
47 | holder.itemView.findViewById(R.id.textView3).text = modList[position].description
48 |
49 | if(modList[position].icon_url!!.isNotEmpty()) {
50 | RithleSingleton.getInstance(appContext).imageLoader.get(
51 | modList[position].icon_url.toString(),
52 | object : ImageLoader.ImageListener {
53 | override fun onResponse(
54 | response: ImageLoader.ImageContainer?,
55 | isImmediate: Boolean
56 | ) {
57 | if (response != null) {
58 | holder.itemView.findViewById(R.id.imageView)
59 | .setImageBitmap(response.bitmap)
60 | }
61 | }
62 |
63 | override fun onErrorResponse(error: VolleyError?) {
64 | Log.e("imageLoader", error!!.stackTraceToString())
65 | }
66 | })
67 | }
68 |
69 | holder.itemView.rootView.setOnClickListener {
70 | val mainFragmentTransaction = fragmentManager.beginTransaction()
71 | val projectViewFragment = ProjectViewFragment.newInstance(modList[position].project_id.toString())
72 |
73 | mainFragmentTransaction
74 | .addToBackStack("projectViewFragment")
75 | .add(R.id.parentFragmentContainer, projectViewFragment)
76 | .commit()
77 | }
78 | }
79 |
80 | override fun getItemCount() = modList.size
81 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/adapters/DownloadsAdapter.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.adapters
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.Context
5 | import android.net.Uri
6 | import android.util.Log
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 | import androidx.browser.customtabs.CustomTabColorSchemeParams
13 | import androidx.browser.customtabs.CustomTabsIntent
14 | import androidx.core.content.ContentProviderCompat.requireContext
15 | import androidx.core.content.ContextCompat
16 | import androidx.fragment.app.FragmentManager
17 | import androidx.recyclerview.widget.RecyclerView
18 | import kotlinx.serialization.json.Json
19 | import me.theclashfruit.rithle.R
20 | import me.theclashfruit.rithle.models.ModrinthSearchHitsModel
21 | import org.json.JSONArray
22 | import org.json.JSONObject
23 |
24 | class DownloadsAdapter(private val downloadsList: JSONArray, private val appContext: Context, private val fragmentManager: FragmentManager) : RecyclerView.Adapter() {
25 |
26 | class StreamHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
27 | init {
28 | itemView.setOnClickListener(this)
29 | }
30 |
31 | override fun onClick(itemView: View) {
32 | Log.d("RecyclerView", "CLICK!")
33 | }
34 | }
35 |
36 | override fun onCreateViewHolder(
37 | parent: ViewGroup,
38 | viewType: Int
39 | ): StreamHolder {
40 | val layoutInflater = LayoutInflater.from(parent.context)
41 |
42 | val view: View = layoutInflater.inflate(R.layout.list_downloads, parent, false)
43 |
44 | return StreamHolder(view)
45 | }
46 |
47 | @SuppressLint("SetTextI18n")
48 | override fun onBindViewHolder(holder: StreamHolder, position: Int) {
49 | val versionNumber: TextView = holder.itemView.findViewById(R.id.textView9)
50 | val versionCompatibility: TextView = holder.itemView.findViewById(R.id.textView10)
51 |
52 | val downloadButton: ImageView = holder.itemView.findViewById(R.id.imageView3)
53 |
54 | val data = downloadsList.getJSONObject(position)
55 | var versionList = ""
56 |
57 | for (i in 0 until data.getJSONArray("game_versions").length()) {
58 | versionList = if (i == 0)
59 | data.getJSONArray("game_versions").getString(i)
60 | else
61 | versionList + ", " + data.getJSONArray("game_versions").getString(i)
62 | }
63 |
64 | versionNumber.text = data.getString("version_number")
65 | versionCompatibility.text = versionList
66 |
67 | downloadButton.setOnClickListener {
68 | CustomTabsIntent.Builder()
69 | .setColorScheme(CustomTabsIntent.COLOR_SCHEME_SYSTEM)
70 | .setColorSchemeParams(
71 | CustomTabsIntent.COLOR_SCHEME_DARK,
72 | CustomTabColorSchemeParams.Builder()
73 | .setToolbarColor(
74 | ContextCompat.getColor(
75 | appContext,
76 | R.color.colorPrimary
77 | )
78 | )
79 | .build()
80 | )
81 | .setDefaultColorSchemeParams(
82 | CustomTabColorSchemeParams.Builder()
83 | .setToolbarColor(
84 | ContextCompat.getColor(
85 | appContext,
86 | R.color.colorPrimaryLight
87 | )
88 | )
89 | .build()
90 | )
91 | .build()
92 | .launchUrl(appContext, Uri.parse(data.getJSONArray("files").getJSONObject(0).getString("url")))
93 | }
94 | }
95 |
96 | override fun getItemCount() = downloadsList.length()
97 | }
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/services/NotificationService.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.services
2 |
3 | import android.app.*
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Build
7 | import android.os.Handler
8 | import android.os.IBinder
9 | import android.util.Log
10 | import androidx.core.app.NotificationCompat
11 | import androidx.core.app.NotificationManagerCompat
12 | import com.android.volley.toolbox.JsonArrayRequest
13 | import me.theclashfruit.rithle.BuildConfig
14 | import me.theclashfruit.rithle.R
15 | import me.theclashfruit.rithle.classes.MrApiUrlUtil
16 | import me.theclashfruit.rithle.classes.RithleSingleton
17 | import java.time.Instant
18 | import java.time.format.DateTimeFormatter
19 | import java.time.temporal.TemporalAccessor
20 | import java.util.*
21 |
22 |
23 | class NotificationService : Service() {
24 | private val theHandler = Handler();
25 |
26 | override fun onCreate() {
27 | val checkNotifs: Runnable = object : Runnable {
28 | override fun run() {
29 | val sharedPref = applicationContext.getSharedPreferences("me.theclashfruit.rithle_preferences", Context.MODE_PRIVATE)
30 |
31 | val authToken = sharedPref!!.getString("authToken", "")
32 | val userName = sharedPref.getString("userName", "@me")
33 |
34 | if(MrApiUrlUtil().getIsDebugMode())
35 | return
36 |
37 | val jsonObjectRequest = object : JsonArrayRequest(
38 | Method.GET, "https://api.modrinth.com/v2/user/${userName}/notifications", null,
39 | { response ->
40 | for (i in 0 until response.length()) {
41 | Log.d("shitShit", response.getJSONObject(i).toString())
42 |
43 | val cNotif = response.getJSONObject(i)
44 |
45 | val notification: Notification = NotificationCompat.Builder(applicationContext, "rthl")
46 | .setContentTitle(cNotif.getString("title"))
47 | .setContentText(cNotif.getString("text"))
48 | .setSmallIcon(R.drawable.ic_update)
49 | .setColor(0x1bd96b)
50 | .setPriority(NotificationCompat.PRIORITY_DEFAULT)
51 | .build()
52 |
53 | val timeFormatter: DateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME
54 | val accessor: TemporalAccessor = timeFormatter.parse(cNotif.getString("created"))
55 |
56 | val date = Date.from(Instant.from(accessor))
57 |
58 | with(NotificationManagerCompat.from(applicationContext)) {
59 | notify(date.time.toInt(), notification)
60 | }
61 | }
62 | },
63 | { error ->
64 | Log.e("webCall", error.stackTraceToString())
65 | }
66 | ) {
67 | override fun getHeaders(): MutableMap {
68 | val headers = HashMap()
69 | headers["User-Agent"] = "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}) Rithle/${BuildConfig.VERSION_NAME} (github.com/TheClashFruit/Rithle; admin@theclashfruit.me) Volley/1.2.1"
70 | headers["Authorization"] = authToken.toString()
71 | return headers
72 | }
73 | }
74 |
75 | RithleSingleton.getInstance(applicationContext).addToRequestQueue(jsonObjectRequest)
76 | }
77 | }
78 |
79 | checkNotifs.run()
80 |
81 | val channel = NotificationChannel("rthl", "Rithle Notification Service", NotificationManager.IMPORTANCE_DEFAULT)
82 | val notificationManager: NotificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
83 |
84 | notificationManager.createNotificationChannel(channel)
85 |
86 | super.onCreate()
87 | }
88 |
89 | override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
90 | val notification: Notification = NotificationCompat.Builder(this, "rthl")
91 | .setContentTitle("Subscribed to the Notifications")
92 | .setContentText("Service is running...")
93 | .setSmallIcon(R.drawable.ic_info)
94 | .setColor(0x1bd96b)
95 | .build()
96 |
97 | startForeground(1, notification)
98 |
99 | with(NotificationManagerCompat.from(this)) {
100 | cancel(1)
101 | }
102 |
103 | return START_STICKY
104 | }
105 |
106 | override fun onBind(intent: Intent): IBinder? {
107 | return null
108 | }
109 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/fragments/ListFragment.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.fragments
2 |
3 | import android.content.Context
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.core.widget.NestedScrollView
11 | import androidx.fragment.app.Fragment
12 | import androidx.recyclerview.widget.DiffUtil
13 | import androidx.recyclerview.widget.LinearLayoutManager
14 | import androidx.recyclerview.widget.RecyclerView
15 | import com.android.volley.toolbox.JsonObjectRequest
16 | import kotlinx.serialization.decodeFromString
17 | import kotlinx.serialization.json.Json
18 | import me.theclashfruit.rithle.BuildConfig
19 | import me.theclashfruit.rithle.R
20 | import me.theclashfruit.rithle.adapters.ModListAdapter
21 | import me.theclashfruit.rithle.classes.ListDiffCallback
22 | import me.theclashfruit.rithle.classes.MrApiUrlUtil
23 | import me.theclashfruit.rithle.classes.RithleSingleton
24 | import me.theclashfruit.rithle.models.ModrinthSearchHitsModel
25 | import me.theclashfruit.rithle.models.ModrinthSearchModel
26 |
27 | class ListFragment : Fragment() {
28 | private var currentIndex = 10
29 | private var lastIndex = 0
30 |
31 | private var allData: ArrayList = arrayListOf()
32 |
33 | private var listAdapter: ModListAdapter? = null
34 | private var layoutManager: LinearLayoutManager? = null
35 |
36 | private var recyclerView: RecyclerView? = null
37 | private var nestedScrollView: NestedScrollView? = null
38 |
39 | private var filter: String? = null
40 |
41 | override fun onCreate(savedInstanceState: Bundle?) {
42 | super.onCreate(savedInstanceState)
43 | arguments?.let {
44 | filter = it.getString("projectFilters")
45 | }
46 | }
47 |
48 | override fun onCreateView(
49 | inflater: LayoutInflater, container: ViewGroup?,
50 | savedInstanceState: Bundle?
51 | ): View? {
52 | val rootView = inflater.inflate(R.layout.fragment_list, container, false)
53 |
54 | recyclerView = rootView.findViewById(R.id.recyclerView)
55 | nestedScrollView = rootView.findViewById(R.id.nestedScrollView)
56 |
57 | listAdapter = ModListAdapter(allData, requireContext(), parentFragmentManager)
58 | layoutManager = LinearLayoutManager(requireContext())
59 |
60 | recyclerView!!.layoutManager = layoutManager
61 | recyclerView!!.adapter = listAdapter
62 |
63 | getItems(requireContext())
64 |
65 | nestedScrollView!!.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
66 | if (scrollY == v.getChildAt(0).measuredHeight - v.measuredHeight)
67 | getItems(requireContext())
68 | })
69 |
70 | return rootView
71 | }
72 |
73 | companion object {
74 | @JvmStatic
75 | fun newInstance(projectFilters: String) =
76 | ListFragment().apply {
77 | arguments = Bundle().apply {
78 | putString("projectFilters", projectFilters)
79 | }
80 | }
81 | }
82 |
83 | private fun getItems(context: Context) {
84 | val format = Json { ignoreUnknownKeys = true }
85 |
86 | Log.d("YesFilter", MrApiUrlUtil().getApiUrl() + "/v2/search?limit=${currentIndex}&offset=${lastIndex}&index=relevance&facets=${filter}")
87 |
88 | val jsonObjectRequest = object : JsonObjectRequest(
89 | Method.GET, MrApiUrlUtil().getApiUrl() + "/v2/search?limit=${currentIndex}&offset=${lastIndex}&index=relevance&facets=${filter}", null,
90 | { response ->
91 | currentIndex = 10
92 | lastIndex += 10
93 |
94 | val data = format.decodeFromString(response.toString())
95 |
96 | for (hit in data.hits) {
97 | allData.add(hit)
98 |
99 | val oldAllData = allData
100 |
101 | DiffUtil.calculateDiff(ListDiffCallback(oldAllData, allData))
102 | .dispatchUpdatesTo(listAdapter!!)
103 |
104 | recyclerView!!.adapter = listAdapter
105 | }
106 | },
107 | { error ->
108 | Log.e("webCall", error.toString())
109 | }
110 | ) {
111 | override fun getHeaders(): MutableMap {
112 | val headers = HashMap()
113 | headers["User-Agent"] = "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}) Rithle/${BuildConfig.VERSION_NAME} (github.com/TheClashFruit/Rithle; admin@theclashfruit.me) Volley/1.2.1"
114 | return headers
115 | }
116 | }
117 |
118 | RithleSingleton.getInstance(context).addToRequestQueue(jsonObjectRequest)
119 | }
120 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/fragments/ProjectViewFragment.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.fragments
2 |
3 | import android.annotation.SuppressLint
4 | import android.os.Build
5 | import android.os.Bundle
6 | import android.util.Log
7 | import android.view.LayoutInflater
8 | import android.view.View
9 | import android.view.ViewGroup
10 | import androidx.fragment.app.Fragment
11 | import com.android.volley.toolbox.JsonObjectRequest
12 | import com.google.android.material.appbar.MaterialToolbar
13 | import com.google.android.material.bottomnavigation.BottomNavigationView
14 | import kotlinx.serialization.decodeFromString
15 | import kotlinx.serialization.json.Json
16 | import me.theclashfruit.rithle.BuildConfig
17 | import me.theclashfruit.rithle.R
18 | import me.theclashfruit.rithle.classes.MrApiUrlUtil
19 | import me.theclashfruit.rithle.classes.RithleSingleton
20 | import me.theclashfruit.rithle.models.ModrinthProjectModel
21 |
22 | class ProjectViewFragment : Fragment() {
23 | private var modId: String? = null
24 | private var dataRaw: String? = null
25 |
26 | override fun onCreate(savedInstanceState: Bundle?) {
27 | super.onCreate(savedInstanceState)
28 | arguments?.let {
29 | modId = it.getString("modId")
30 | }
31 | }
32 |
33 | @SuppressLint("MissingInflatedId")
34 | override fun onCreateView(
35 | inflater: LayoutInflater, container: ViewGroup?,
36 | savedInstanceState: Bundle?
37 | ): View? {
38 | val rootView = inflater.inflate(R.layout.fragment_project_view, container, false)
39 |
40 | val toolBar: MaterialToolbar = rootView.findViewById(R.id.toolbar)
41 | val bottomNavBar: BottomNavigationView = rootView.findViewById(R.id.bottomNavigation)
42 |
43 | toolBar.setNavigationOnClickListener {
44 | parentFragmentManager.popBackStack()
45 | }
46 |
47 | bottomNavBar.setOnNavigationItemSelectedListener { item ->
48 | val bottomNavFragmentTransaction = parentFragmentManager.beginTransaction()
49 |
50 | when(item.itemId) {
51 | R.id.itemInfo -> {
52 | val infoFragment = ProjectInfoFragment.newInstance(dataRaw!!)
53 |
54 | bottomNavFragmentTransaction
55 | .replace(R.id.projectFragmentContainer, infoFragment)
56 | .commit()
57 |
58 | return@setOnNavigationItemSelectedListener true
59 | }
60 | R.id.itemChangelog -> {
61 | val descriptionFragment = ProjectDescriptionFragment.newInstance(dataRaw!!)
62 |
63 | bottomNavFragmentTransaction
64 | .replace(R.id.projectFragmentContainer, descriptionFragment)
65 | .commit()
66 |
67 | return@setOnNavigationItemSelectedListener true
68 | }
69 | R.id.itemDownloads -> {
70 | val downloadsFragment = ProjectDownloadsFragment.newInstance(modId)
71 |
72 | bottomNavFragmentTransaction
73 | .replace(R.id.projectFragmentContainer, downloadsFragment)
74 | .commit()
75 |
76 | return@setOnNavigationItemSelectedListener true
77 | }
78 | R.id.itemSettings -> {
79 |
80 | return@setOnNavigationItemSelectedListener true
81 | }
82 | else -> false
83 | }
84 | }
85 |
86 | val format = Json { ignoreUnknownKeys = true }
87 |
88 | val jsonObjectRequest = @SuppressLint("SetTextI18n")
89 | object : JsonObjectRequest(
90 | Method.GET, MrApiUrlUtil().getApiUrl() + "/v2/project/${modId}", null,
91 | { response ->
92 | dataRaw = response.toString()
93 | val dataJson = format.decodeFromString(response.toString())
94 |
95 | toolBar.subtitle = dataJson.title
96 |
97 | val infoFragment = ProjectInfoFragment.newInstance(dataRaw!!)
98 |
99 | parentFragmentManager.beginTransaction()
100 | .replace(R.id.projectFragmentContainer, infoFragment)
101 | .commit()
102 | },
103 | { error ->
104 | Log.e("webCall", error.toString())
105 | }
106 | ) {
107 | override fun getHeaders(): MutableMap {
108 | val headers = HashMap()
109 | headers["User-Agent"] = "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}) Rithle/${BuildConfig.VERSION_NAME} (github.com/TheClashFruit/Rithle; admin@theclashfruit.me) Volley/1.2.1"
110 | return headers
111 | }
112 | }
113 |
114 | RithleSingleton.getInstance(requireContext()).addToRequestQueue(jsonObjectRequest)
115 |
116 | return rootView
117 | }
118 |
119 | companion object {
120 | @JvmStatic
121 | fun newInstance(modId: String) =
122 | ProjectViewFragment().apply {
123 | arguments = Bundle().apply {
124 | putString("modId", modId)
125 | }
126 | }
127 | }
128 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/AuthActivity.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle
2 |
3 | import android.content.Context
4 | import android.net.Uri
5 | import android.os.Build
6 | import androidx.appcompat.app.AppCompatActivity
7 | import android.os.Bundle
8 | import android.util.Log
9 | import android.widget.TextView
10 | import androidx.recyclerview.widget.DiffUtil
11 | import com.android.volley.Request
12 | import com.android.volley.toolbox.JsonObjectRequest
13 | import com.android.volley.toolbox.StringRequest
14 | import kotlinx.serialization.decodeFromString
15 | import kotlinx.serialization.json.Json
16 | import me.theclashfruit.rithle.classes.ListDiffCallback
17 | import me.theclashfruit.rithle.classes.MrApiUrlUtil
18 | import me.theclashfruit.rithle.classes.RithleSingleton
19 | import me.theclashfruit.rithle.models.GitHubAccessTokenModel
20 | import me.theclashfruit.rithle.models.ModrinthSearchModel
21 |
22 | class AuthActivity : AppCompatActivity() {
23 | private var progressTextView: TextView? = null
24 |
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | setContentView(R.layout.activity_auth)
28 |
29 | val action: String? = intent?.action
30 | val data: Uri? = intent?.data
31 |
32 | progressTextView = findViewById(R.id.progressTextView)
33 |
34 | val sharedPref = getSharedPreferences("me.theclashfruit.rithle_preferences", Context.MODE_PRIVATE)
35 |
36 | val format = Json { ignoreUnknownKeys = true }
37 |
38 | // https://api.modrinth.com/v2/user
39 |
40 | val jsonObjectRequest = object : StringRequest(
41 | Method.POST, "https://github.com/login/oauth/access_token",
42 | { response ->
43 | val responseJson = format.decodeFromString(response)
44 |
45 | sharedPref.edit()
46 | .putString("authToken", responseJson.access_token!!)
47 | .apply()
48 |
49 | getMrUser(responseJson.access_token!!)
50 |
51 | progressTextView!!.text = "Requesting user from Modrinth..."
52 |
53 | Log.d("AuthUri", responseJson.access_token!!)
54 | },
55 | { error ->
56 | progressTextView!!.text = resources.getString(R.string.auth_error_ghstep)
57 |
58 | error.networkResponse.allHeaders?.forEach {
59 | Log.e("webCall", it.name + ": " + it.value)
60 | }
61 |
62 | Log.e("webCall", "Code: " + error.networkResponse.statusCode.toString())
63 | Log.e("webCall", "Data: " + error.networkResponse.data.decodeToString())
64 | Log.e("webCall", "Time: " + error.networkResponse.networkTimeMs.toString())
65 | }
66 | ) {
67 | override fun getParams(): Map {
68 | val params: MutableMap = HashMap()
69 | params["client_id"] = BuildConfig.GH_CLIENT
70 | params["client_secret"] = BuildConfig.GH_SECRET
71 | params["code"] = data!!.getQueryParameter("code")!!.trim()
72 | return params
73 | }
74 |
75 | override fun getHeaders(): MutableMap {
76 | val headers = HashMap()
77 | headers["User-Agent"] = "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}) Rithle/${BuildConfig.VERSION_NAME} (github.com/TheClashFruit/Rithle; admin@theclashfruit.me) Volley/1.2.1"
78 | headers["Accept"] = "application/json"
79 | headers["Content-Type"] = "application/x-www-form-urlencoded; charset=utf-8"
80 | return headers
81 | }
82 | }
83 |
84 | RithleSingleton.getInstance(this).addToRequestQueue(jsonObjectRequest)
85 | }
86 |
87 | fun getMrUser(accessToken: String) {
88 | val sharedPref = getSharedPreferences("me.theclashfruit.rithle_preferences", Context.MODE_PRIVATE)
89 |
90 | val jsonObjectRequest = object : JsonObjectRequest(
91 | Method.GET, MrApiUrlUtil().getApiUrl() + "/v2/user", null,
92 | { response ->
93 | progressTextView!!.text = "Welcome ${response.getString("username")}!"
94 |
95 | sharedPref.edit()
96 | .putString("userName", response.getString("username"))
97 | .apply()
98 |
99 | finish()
100 | },
101 | { error ->
102 | progressTextView!!.text = resources.getString(R.string.auth_error_mrstep, accessToken)
103 | Log.e("webCall", error.stackTraceToString())
104 | }
105 | ) {
106 | override fun getHeaders(): MutableMap {
107 | val headers = HashMap()
108 | headers["User-Agent"] = "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}) Rithle/${BuildConfig.VERSION_NAME} (github.com/TheClashFruit/Rithle; admin@theclashfruit.me) Volley/1.2.1"
109 | headers["Authorization"] = accessToken
110 | return headers
111 | }
112 | }
113 |
114 | RithleSingleton.getInstance(this).addToRequestQueue(jsonObjectRequest)
115 | }
116 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/fragments/UserFragment.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.fragments
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 | import android.os.Build
7 | import android.os.Bundle
8 | import android.util.Log
9 | import android.view.LayoutInflater
10 | import android.view.View
11 | import android.view.ViewGroup
12 | import android.widget.TextView
13 | import androidx.browser.customtabs.CustomTabColorSchemeParams
14 | import androidx.browser.customtabs.CustomTabsIntent
15 | import androidx.core.content.ContextCompat
16 | import androidx.fragment.app.Fragment
17 | import com.android.volley.toolbox.JsonObjectRequest
18 | import com.google.android.material.appbar.MaterialToolbar
19 | import me.theclashfruit.rithle.BuildConfig
20 | import me.theclashfruit.rithle.R
21 | import me.theclashfruit.rithle.classes.MrApiUrlUtil
22 | import me.theclashfruit.rithle.classes.RithleSingleton
23 | import java.text.NumberFormat
24 | import java.util.*
25 | import kotlin.collections.HashMap
26 |
27 | class UserFragment : Fragment() {
28 | private var userId: String? = null
29 |
30 | override fun onCreate(savedInstanceState: Bundle?) {
31 | super.onCreate(savedInstanceState)
32 | arguments?.let {
33 |
34 | }
35 | }
36 |
37 | override fun onCreateView(
38 | inflater: LayoutInflater, container: ViewGroup?,
39 | savedInstanceState: Bundle?
40 | ): View? {
41 | val rootView = inflater.inflate(R.layout.fragment_user, container, false)
42 |
43 | val sharedPref = activity?.getSharedPreferences("me.theclashfruit.rithle_preferences", Context.MODE_PRIVATE)
44 |
45 | val authToken = sharedPref!!.getString("authToken", "")
46 |
47 | if(authToken == "") {
48 | CustomTabsIntent.Builder()
49 | .setColorScheme(CustomTabsIntent.COLOR_SCHEME_SYSTEM)
50 | .setColorSchemeParams(
51 | CustomTabsIntent.COLOR_SCHEME_DARK,
52 | CustomTabColorSchemeParams.Builder()
53 | .setToolbarColor(ContextCompat.getColor(requireContext(), R.color.colorPrimary))
54 | .build()
55 | )
56 | .setDefaultColorSchemeParams(
57 | CustomTabColorSchemeParams.Builder()
58 | .setToolbarColor(ContextCompat.getColor(requireContext(), R.color.colorPrimaryLight))
59 | .build()
60 | )
61 | .build()
62 | .launchUrl(requireContext(), Uri.parse("https://github.com/login/oauth/authorize?client_id=2f7fbf1e6e196b0d2069"))
63 |
64 | parentFragmentManager.popBackStack()
65 | }
66 |
67 | val jsonObjectRequest = object : JsonObjectRequest(
68 | Method.GET, MrApiUrlUtil().getApiUrl() + "/v2/user", null,
69 | { response ->
70 | val format: NumberFormat = NumberFormat.getCurrencyInstance()
71 | format.maximumFractionDigits = 0
72 | format.currency = Currency.getInstance("USD")
73 | format.maximumFractionDigits = 2
74 |
75 | rootView.findViewById(R.id.textView7).text = "${response.getString("username")} [${response.getString("role").capitalize()}]"
76 | rootView.findViewById(R.id.textView8).text = "${format.format(response.getJSONObject("payout_data").getString("balance").toFloat())} earned so far.."
77 |
78 | userId = response.getString("username")
79 |
80 | Log.e("webCall", response.toString())
81 | },
82 | { error ->
83 | Log.e("webCall", error.stackTraceToString())
84 | }
85 | ) {
86 | override fun getHeaders(): MutableMap {
87 | val headers = HashMap()
88 | headers["User-Agent"] = "Mozilla/5.0 (Linux; Android ${Build.VERSION.RELEASE}) Rithle/${BuildConfig.VERSION_NAME} (github.com/TheClashFruit/Rithle; admin@theclashfruit.me) Volley/1.2.1"
89 | headers["Authorization"] = authToken.toString()
90 | return headers
91 | }
92 | }
93 |
94 | RithleSingleton.getInstance(requireContext()).addToRequestQueue(jsonObjectRequest)
95 |
96 | val toolBar: MaterialToolbar = rootView.findViewById(R.id.toolbar)
97 |
98 | toolBar.setNavigationOnClickListener {
99 | parentFragmentManager.popBackStack()
100 | }
101 |
102 | toolBar.setOnMenuItemClickListener { item ->
103 | when(item.itemId) {
104 | R.id.toolbarEdit -> {
105 | return@setOnMenuItemClickListener true
106 | }
107 | R.id.toolbarShare -> {
108 | val sendIntent: Intent = Intent().apply {
109 | action = Intent.ACTION_SEND
110 | putExtra(Intent.EXTRA_TEXT, "https://modrinth.com/user/$userId")
111 | type = "text/plain"
112 | }
113 |
114 | val shareIntent = Intent.createChooser(sendIntent, null)
115 | startActivity(shareIntent)
116 |
117 | return@setOnMenuItemClickListener true
118 | }
119 | else -> false
120 | }
121 | }
122 |
123 | return rootView
124 | }
125 |
126 | companion object {
127 | @JvmStatic
128 | fun newInstance() =
129 | UserFragment().apply {
130 | arguments = Bundle().apply {
131 |
132 | }
133 | }
134 | }
135 | }
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/fragments/HomeFragment.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.fragments
2 |
3 | import android.os.Bundle
4 | import android.util.Log
5 | import androidx.fragment.app.Fragment
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import com.google.android.material.appbar.MaterialToolbar
10 | import com.google.android.material.bottomnavigation.BottomNavigationView
11 | import me.theclashfruit.rithle.classes.FilterBuilder
12 | import me.theclashfruit.rithle.R
13 |
14 | class HomeFragment : Fragment() {
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | arguments?.let {
18 |
19 | }
20 | }
21 |
22 | override fun onCreateView(
23 | inflater: LayoutInflater, container: ViewGroup?,
24 | savedInstanceState: Bundle?
25 | ): View? {
26 | val rootView = inflater.inflate(R.layout.fragment_home, container, false)
27 |
28 | val bottomNav = rootView.findViewById(R.id.bottomNavigation)
29 | val toolBar = rootView.findViewById(R.id.toolbar)
30 |
31 | val filter = FilterBuilder()
32 | .setProjectType("mod")
33 | .addFilterItem("categories:'forge'")
34 | .addFilterItem("categories:'fabric'")
35 | .addFilterItem("categories:'quilt'")
36 | .addFilterItem("categories:'liteloader'")
37 | .addFilterItem("categories:'modloader'")
38 | .addFilterItem("categories:'rift'")
39 | .build()
40 |
41 | val fragmentTransaction = parentFragmentManager.beginTransaction()
42 | var listFragment = ListFragment.newInstance(filter)
43 |
44 | fragmentTransaction
45 | .replace(R.id.fragmentContainer, listFragment)
46 | .commit()
47 |
48 | Log.w("YesFilter", filter)
49 |
50 | toolBar.setOnMenuItemClickListener { item ->
51 | val toolBarFragmentTransaction = parentFragmentManager.beginTransaction()
52 |
53 | when(item.itemId) {
54 | R.id.toolBarSearch -> {
55 | return@setOnMenuItemClickListener true
56 | }
57 | R.id.toolBarAccount -> {
58 | val userFragment = UserFragment.newInstance()
59 |
60 | toolBarFragmentTransaction
61 | .addToBackStack("userFragment")
62 | .add(R.id.parentFragmentContainer, userFragment)
63 | .commit()
64 |
65 | return@setOnMenuItemClickListener true
66 | }
67 | R.id.toolBarSettings -> {
68 | val settingsFragment = SettingsContainerFragment.newInstance()
69 |
70 | toolBarFragmentTransaction
71 | .addToBackStack("settingsFragment")
72 | .add(R.id.parentFragmentContainer, settingsFragment)
73 | .commit()
74 |
75 | return@setOnMenuItemClickListener true
76 | }
77 | else -> false
78 | }
79 | }
80 |
81 | bottomNav.setOnNavigationItemSelectedListener { item ->
82 | val bottomNavFragmentTransaction = parentFragmentManager.beginTransaction()
83 |
84 | when(item.itemId) {
85 | R.id.itemMods -> {
86 | val fragmentFilter = FilterBuilder()
87 | .setProjectType("mod")
88 | .addFilterItem("categories:'forge'")
89 | .addFilterItem("categories:'fabric'")
90 | .addFilterItem("categories:'quilt'")
91 | .addFilterItem("categories:'liteloader'")
92 | .addFilterItem("categories:'modloader'")
93 | .addFilterItem("categories:'rift'")
94 | .build()
95 |
96 | listFragment = ListFragment.newInstance(fragmentFilter)
97 |
98 | bottomNavFragmentTransaction
99 | .replace(R.id.fragmentContainer, listFragment)
100 | .commit()
101 |
102 | return@setOnNavigationItemSelectedListener true
103 | }
104 | R.id.itemPlugins -> {
105 | val fragmentFilter = FilterBuilder()
106 | .setProjectType("mod")
107 | .addFilterItem("categories:'bukkit'")
108 | .addFilterItem("categories:'spigot'")
109 | .addFilterItem("categories:'paper'")
110 | .addFilterItem("categories:'purpur'")
111 | .addFilterItem("categories:'sponge'")
112 | .addFilterItem("categories:'bungeecord'")
113 | .addFilterItem("categories:'waterfall'")
114 | .addFilterItem("categories:'velocity'")
115 | .build()
116 |
117 | listFragment = ListFragment.newInstance(fragmentFilter)
118 |
119 | bottomNavFragmentTransaction
120 | .replace(R.id.fragmentContainer, listFragment)
121 | .commit()
122 |
123 | return@setOnNavigationItemSelectedListener true
124 | }
125 | R.id.itemResourcePacks -> {
126 | val fragmentFilter = FilterBuilder()
127 | .setProjectType("resourcepack")
128 | .build()
129 |
130 | listFragment = ListFragment.newInstance(fragmentFilter)
131 |
132 | bottomNavFragmentTransaction
133 | .replace(R.id.fragmentContainer, listFragment)
134 | .commit()
135 |
136 | return@setOnNavigationItemSelectedListener true
137 | }
138 | R.id.itemModpacks -> {
139 | val fragmentFilter = FilterBuilder()
140 | .setProjectType("modpack")
141 | .build()
142 |
143 | listFragment = ListFragment.newInstance(fragmentFilter)
144 |
145 | bottomNavFragmentTransaction
146 | .replace(R.id.fragmentContainer, listFragment)
147 | .commit()
148 |
149 | return@setOnNavigationItemSelectedListener true
150 | }
151 | else -> false
152 | }
153 | }
154 |
155 | return rootView
156 | }
157 |
158 | companion object {
159 | @JvmStatic
160 | fun newInstance() =
161 | HomeFragment().apply {
162 | arguments = Bundle().apply {
163 |
164 | }
165 | }
166 | }
167 | }
--------------------------------------------------------------------------------
/app/src/main/java/me/theclashfruit/rithle/fragments/ProjectInfoFragment.kt:
--------------------------------------------------------------------------------
1 | package me.theclashfruit.rithle.fragments
2 |
3 | import android.content.Context
4 | import android.graphics.BitmapFactory
5 | import android.graphics.drawable.BitmapDrawable
6 | import android.net.Uri
7 | import android.os.Bundle
8 | import android.util.Log
9 | import android.view.LayoutInflater
10 | import android.view.View
11 | import android.view.ViewGroup
12 | import android.widget.ImageView
13 | import android.widget.TextView
14 | import androidx.browser.customtabs.CustomTabColorSchemeParams
15 | import androidx.browser.customtabs.CustomTabsIntent
16 | import androidx.core.content.ContextCompat
17 | import androidx.fragment.app.Fragment
18 | import com.android.volley.VolleyError
19 | import com.android.volley.toolbox.ImageLoader
20 | import io.noties.markwon.AbstractMarkwonPlugin
21 | import io.noties.markwon.Markwon
22 | import io.noties.markwon.MarkwonConfiguration
23 | import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
24 | import io.noties.markwon.ext.tables.TablePlugin
25 | import io.noties.markwon.html.HtmlPlugin
26 | import io.noties.markwon.image.ImageItem
27 | import io.noties.markwon.image.ImagesPlugin
28 | import io.noties.markwon.linkify.LinkifyPlugin
29 | import kotlinx.serialization.decodeFromString
30 | import kotlinx.serialization.json.Json
31 | import me.theclashfruit.rithle.R
32 | import me.theclashfruit.rithle.classes.ProxySchemeHandler
33 | import me.theclashfruit.rithle.classes.RithleSingleton
34 | import me.theclashfruit.rithle.models.ModrinthProjectModel
35 | import java.net.URL
36 | import java.text.NumberFormat
37 | import java.util.*
38 |
39 |
40 | class ProjectInfoFragment : Fragment() {
41 | private var projectData: ModrinthProjectModel? = null
42 |
43 | override fun onCreate(savedInstanceState: Bundle?) {
44 | super.onCreate(savedInstanceState)
45 |
46 | val format = Json { ignoreUnknownKeys = true }
47 |
48 | arguments?.let {
49 | projectData = format.decodeFromString(it.getString("projectData")!!)
50 | }
51 | }
52 |
53 | override fun onCreateView(
54 | inflater: LayoutInflater, container: ViewGroup?,
55 | savedInstanceState: Bundle?
56 | ): View? {
57 | val rootView = inflater.inflate(R.layout.fragment_project_info, container, false)
58 |
59 | val projectIcon = rootView.findViewById(R.id.imageView)
60 |
61 | val nf = NumberFormat.getInstance(Locale.UK)
62 |
63 | val textViewTitle = rootView.findViewById(R.id.textViewTitle)
64 | val textViewDownloads = rootView.findViewById(R.id.textViewDownloads)
65 | val textViewFollowers = rootView.findViewById(R.id.textViewFollowers)
66 |
67 | textViewTitle.text = projectData!!.title
68 | textViewDownloads.text = resources.getQuantityString(R.plurals.project_downloads, projectData!!.downloads!!.toInt(), nf.format(projectData!!.downloads!!.toLong()).toString())
69 | textViewFollowers.text = resources.getQuantityString(R.plurals.project_followers, projectData!!.followers!!.toInt(), nf.format(projectData!!.followers!!.toLong()).toString())
70 |
71 | val markwon = Markwon.builder(requireContext())
72 | .usePlugin(HtmlPlugin.create())
73 | .usePlugin(TablePlugin.create(requireContext()))
74 | .usePlugin(StrikethroughPlugin.create())
75 | .usePlugin(LinkifyPlugin.create())
76 | .usePlugin(ImagesPlugin.create { plugin ->
77 | // for example to return a drawable resource
78 | plugin.addSchemeHandler(object : ProxySchemeHandler() {
79 | override fun handle(raw: String, uri: Uri): ImageItem {
80 | val sharedPref = activity!!.getSharedPreferences("me.theclashfruit.rithle_preferences", Context.MODE_PRIVATE)
81 | val doProxy = sharedPref!!.getBoolean("imageProxy", false)
82 |
83 | val url: URL =
84 | if(doProxy)
85 | URL("https://rthl.theclashfruit.me/img.php?url=$uri")
86 | else
87 | URL(uri.toString())
88 |
89 | Log.d("ThatUrl", url.toString())
90 |
91 | val image = BitmapFactory.decodeStream(url.openConnection().getInputStream());
92 |
93 | return ImageItem.withResult(BitmapDrawable(resources, image))
94 | }
95 |
96 | override fun supportedSchemes(): Collection {
97 | return listOf("http", "https")
98 | }
99 | })
100 | })
101 | .usePlugin(object : AbstractMarkwonPlugin() {
102 | override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
103 | builder.linkResolver { view, link ->
104 | view.callOnClick()
105 |
106 | Log.w("UriLog", view.paddingTop.toString())
107 | Log.w("UriLog", link)
108 |
109 | CustomTabsIntent.Builder()
110 | .setColorScheme(CustomTabsIntent.COLOR_SCHEME_SYSTEM)
111 | .setColorSchemeParams(
112 | CustomTabsIntent.COLOR_SCHEME_DARK,
113 | CustomTabColorSchemeParams.Builder()
114 | .setToolbarColor(ContextCompat.getColor(requireContext(), R.color.colorPrimary))
115 | .build()
116 | )
117 | .setDefaultColorSchemeParams(
118 | CustomTabColorSchemeParams.Builder()
119 | .setToolbarColor(ContextCompat.getColor(requireContext(), R.color.colorPrimaryLight))
120 | .build()
121 | )
122 | .build()
123 | .launchUrl(requireContext(), Uri.parse(link))
124 | }
125 | }
126 | })
127 | .build()
128 |
129 |
130 | markwon.setParsedMarkdown(rootView.findViewById(R.id.textViewDescription), markwon.render(markwon.parse(projectData!!.body!!)))
131 |
132 | if(projectData!!.icon_url != null) {
133 | RithleSingleton.getInstance(requireContext()).imageLoader.get(projectData!!.icon_url.toString(), object : ImageLoader.ImageListener {
134 | override fun onResponse(response: ImageLoader.ImageContainer?, isImmediate: Boolean) {
135 | if (response != null) {
136 | projectIcon.setImageBitmap(response.bitmap)
137 | }
138 | }
139 |
140 | override fun onErrorResponse(error: VolleyError?) {
141 | Log.d("imageLoader", "wtf are you doing, you either don't have internet or the url is fucking wrong, btw the error is: ${error.toString()}")
142 | }
143 | })
144 | }
145 |
146 | return rootView
147 | }
148 |
149 | companion object {
150 | @JvmStatic
151 | fun newInstance(projectDataString: String) =
152 | ProjectInfoFragment().apply {
153 | arguments = Bundle().apply {
154 | putString("projectData", projectDataString)
155 | }
156 | }
157 | }
158 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_project_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
18 |
19 |
23 |
24 |
27 |
28 |
29 |
41 |
42 |
46 |
47 |
57 |
58 |
63 |
64 |
65 |
66 |
79 |
80 |
91 |
92 |
103 |
104 |
105 |
106 |
118 |
119 |
123 |
124 |
140 |
141 |
142 |
143 |
144 |
156 |
157 |
161 |
162 |
178 |
179 |
180 |
181 |
182 |
195 |
196 |
200 |
201 |
215 |
216 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
--------------------------------------------------------------------------------