diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml index 68444cd8..313a0be5 100644 --- a/.github/workflows/beta.yml +++ b/.github/workflows/beta.yml @@ -12,6 +12,7 @@ jobs: runs-on: ubuntu-latest env: CI: true + SKIP_BUILD: false steps: - name: Checkout repo @@ -19,14 +20,12 @@ jobs: with: fetch-depth: 0 - - name: Download last SHA artifact uses: dawidd6/action-download-artifact@v3 with: workflow: beta.yml name: last-sha path: . - continue-on-error: true - name: Get Commits Since Last Run @@ -39,7 +38,9 @@ jobs: fi echo "Commits since $LAST_SHA:" # Accumulate commit logs in a shell variable - COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"● %s ~%an") + COMMIT_LOGS=$(git log $LAST_SHA..HEAD --pretty=format:"● %s ~%an [֍](https://github.com/${{ github.repository }}/commit/%H)") + # Replace commit messages with pull request links + COMMIT_LOGS=$(echo "$COMMIT_LOGS" | sed -E 's/#([0-9]+)/[#\1](https:\/\/github.com\/rebelonion\/Dantotsu\/pull\/\1)/g') # URL-encode the newline characters for GitHub Actions COMMIT_LOGS="${COMMIT_LOGS//'%'/'%25'}" COMMIT_LOGS="${COMMIT_LOGS//$'\n'/'%0A'}" @@ -65,26 +66,40 @@ jobs: echo "Version $VERSION" echo "VERSION=$VERSION" >> $GITHUB_ENV + - name: List files in the directory + run: ls -l + - name: Setup JDK 17 + if: ${{ env.SKIP_BUILD != 'true' }} uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: 17 cache: gradle - - - name: Decode Keystore File - run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore - - - name: List files in the directory - run: ls -l - - - name: Make gradlew executable - run: chmod +x ./gradlew - - - name: Build with Gradle - run: ./gradlew assembleGoogleAlpha -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }} + - name: Decode Keystore File + if: ${{ github.repository == 'rebelonion/Dantotsu' }} + run: echo "${{ secrets.KEYSTORE_FILE }}" | base64 -d > $GITHUB_WORKSPACE/key.keystore + + - name: Make gradlew executable + if: ${{ env.SKIP_BUILD != 'true' }} + run: chmod +x ./gradlew + + - name: Build with Gradle + if: ${{ env.SKIP_BUILD != 'true' }} + run: | + if [ "${{ github.repository }}" == "rebelonion/Dantotsu" ]; then + ./gradlew assembleGoogleAlpha \ + -Pandroid.injected.signing.store.file=$GITHUB_WORKSPACE/key.keystore \ + -Pandroid.injected.signing.store.password=${{ secrets.KEYSTORE_PASSWORD }} \ + -Pandroid.injected.signing.key.alias=${{ secrets.KEY_ALIAS }} \ + -Pandroid.injected.signing.key.password=${{ secrets.KEY_PASSWORD }}; + else + ./gradlew assembleGoogleAlpha; + fi + - name: Upload a Build Artifact + if: ${{ env.SKIP_BUILD != 'true' }} uses: actions/upload-artifact@v4 with: name: Dantotsu @@ -93,25 +108,203 @@ jobs: path: "app/build/outputs/apk/google/alpha/app-google-alpha.apk" - name: Upload APK to Discord and Telegram - if: ${{ github.repository == 'rebelonion/Dantotsu' }} shell: bash run: | - #Discord + # Prepare Discord embed + fetch_user_details() { + local login=$1 + user_details=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/users/$login") + name=$(echo "$user_details" | jq -r '.name // .login') + login=$(echo "$user_details" | jq -r '.login') + avatar_url=$(echo "$user_details" | jq -r '.avatar_url') + echo "$name|$login|$avatar_url" + } + + # Additional information for the goats + declare -A additional_info + additional_info["ibo"]="\n Discord: <@951737931159187457>\n AniList: [takarealist112]()" + additional_info["aayush262"]="\n Discord: <@918825160654598224>\n AniList: [aayush262]()" + additional_info["rebelonion"]="\n Discord: <@714249925248024617>\n AniList: [rebelonion]()\n PornHub: [rebelonion]()" + + # Decimal color codes for contributors + declare -A contributor_colors + default_color="#ff25f9" + contributor_colors["ibo"]="#ff7500" + contributor_colors["aayush262"]="#5d689d" + contributor_colors["Sadwhy"]="#ff7e95" + contributor_colors["rebelonion"]="#d4e5ed" + hex_to_decimal() { printf '%d' "0x${1#"#"}"; } + + # Count recent commits and create an associative array + declare -A recent_commit_counts + while read -r count name; do + recent_commit_counts["$name"]=$count + done < <(echo "$COMMIT_LOG" | sed 's/%0A/\n/g' | grep -oP '(?<=~)[^[]*' | sort | uniq -c | sort -rn) + # Fetch contributors from GitHub + contributors=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + "https://api.github.com/repos/${{ github.repository }}/contributors") + + # Create a sorted list of contributors based on recent commit counts + sorted_contributors=$(for login in $(echo "$contributors" | jq -r '.[].login'); do + user_info=$(fetch_user_details "$login") + name=$(echo "$user_info" | cut -d'|' -f1) + count=${recent_commit_counts["$name"]:-0} + echo "$count|$login" + done | sort -rn | cut -d'|' -f2) + + # Initialize needed variables + developers="" + committers_count=0 + max_commits=0 + top_contributor="" + top_contributor_count=0 + top_contributor_avatar="" + embed_color=$default_color + + # Process contributors in the new order + while read -r login; do + user_info=$(fetch_user_details "$login") + name=$(echo "$user_info" | cut -d'|' -f1) + login=$(echo "$user_info" | cut -d'|' -f2) + avatar_url=$(echo "$user_info" | cut -d'|' -f3) + + # Only process if they have recent commits + commit_count=${recent_commit_counts["$name"]:-0} + if [ $commit_count -gt 0 ]; then + # Update top contributor information + if [ $commit_count -gt $max_commits ]; then + max_commits=$commit_count + top_contributors=("$login") + top_contributor_count=1 + top_contributor_avatar="$avatar_url" + embed_color=$(hex_to_decimal "${contributor_colors[$name]:-$default_color}") + elif [ $commit_count -eq $max_commits ]; then + top_contributors+=("$login") + top_contributor_count=$((top_contributor_count + 1)) + embed_color=$default_color + fi + + # Get commit count for this contributor on the dev branch + branch_commit_count=$(git rev-list --count dev --author="$login") + + extra_info="${additional_info[$name]}" + if [ -n "$extra_info" ]; then + extra_info=$(echo "$extra_info" | sed 's/\\n/\n- /g') + fi + + # Construct the developer entry + developer_entry="◗ **${name}** ${extra_info} + - Github: [${login}](https://github.com/${login}) + - Commits: ${branch_commit_count}" + + # Add the entry to developers, with a newline if it's not the first entry + if [ -n "$developers" ]; then + developers="${developers} + ${developer_entry}" + else + developers="${developer_entry}" + fi + committers_count=$((committers_count + 1)) + fi + done <<< "$sorted_contributors" + + # Set the thumbnail URL and color based on top contributor(s) + if [ $top_contributor_count -eq 1 ]; then + thumbnail_url="$top_contributor_avatar" + else + thumbnail_url="https://i.imgur.com/5o3Y9Jb.gif" + embed_color=$default_color + fi + + # Truncate field values + max_length=1000 commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g; s/^/\n/') - # Truncate commit messages if they are too long - max_length=1900 # Adjust this value as needed + if [ ${#developers} -gt $max_length ]; then + developers="${developers:0:$max_length}... (truncated)" + fi if [ ${#commit_messages} -gt $max_length ]; then commit_messages="${commit_messages:0:$max_length}... (truncated)" fi - contentbody=$( jq -nc --arg msg "Alpha-Build: <@&1225347048321191996> **$VERSION**:" --arg commits "$commit_messages" '{"content": ($msg + "\n" + $commits)}' ) - curl -F "payload_json=${contentbody}" -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" ${{ secrets.DISCORD_WEBHOOK }} - - #Telegram - curl -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \ - -F "document=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \ - -F "caption=Alpha-Build: ${VERSION}: ${commit_messages}" \ - https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument + # Construct Discord payload + discord_data=$(jq -nc \ + --arg field_value "$commit_messages" \ + --arg author_value "$developers" \ + --arg footer_text "Version $VERSION" \ + --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%S.000Z)" \ + --arg thumbnail_url "$thumbnail_url" \ + --argjson embed_color "$embed_color" \ + '{ + "content": "<@&1225347048321191996>", + "embeds": [ + { + "title": "New Alpha-Build dropped", + "color": $embed_color, + "fields": [ + { + "name": "Commits:", + "value": $field_value, + "inline": true + }, + { + "name": "Developers:", + "value": $author_value, + "inline": false + } + ], + "footer": { + "text": $footer_text + }, + "timestamp": $timestamp, + "thumbnail": { + "url": $thumbnail_url + } + } + ], + "attachments": [] + }') + + # Send Discord message + curl -H "Content-Type: application/json" \ + -d "$discord_data" \ + ${{ secrets.DISCORD_WEBHOOK }} + echo "You have only send an embed to discord due to SKIP_BUILD being set to true" + + # Upload APK to Discord + if [ "$SKIP_BUILD" != "true" ]; then + curl -F "payload_json=${contentbody}" \ + -F "dantotsu_debug=@app/build/outputs/apk/google/alpha/app-google-alpha.apk" \ + ${{ secrets.DISCORD_WEBHOOK }} + else + echo "Skipping APK upload to Discord due to SKIP_BUILD being set to true" + fi + + # Format commit messages for Telegram + telegram_commit_messages=$(echo "$COMMIT_LOG" | sed 's/%0A/\n/g' | while read -r line; do + message=$(echo "$line" | sed -E 's/● (.*) ~(.*) \[֍\]\((.*)\)/● \1 ~\2 ֍<\/a>/') + message=$(echo "$message" | sed -E 's/\[#([0-9]+)\]\((https:\/\/github\.com\/[^)]+)\)/#\1<\/a>/g') + echo "$message" + done) + telegram_commit_messages="
${telegram_commit_messages}
" + + # Upload APK to Telegram + if [ "$SKIP_BUILD" != "true" ]; then + APK_PATH="app/build/outputs/apk/google/alpha/app-google-alpha.apk" + response=$(curl -sS -f -X POST \ + "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendDocument" \ + -F "chat_id=${{ secrets.TELEGRAM_CHANNEL_ID }}" \ + -F "document=@$APK_PATH" \ + -F "caption=New Alpha-Build dropped 🔥 + + Commits: + ${telegram_commit_messages} + version: ${VERSION}" \ + -F "parse_mode=HTML") + else + echo "Skipping Telegram message and APK upload due to SKIP_BUILD being set to true" + fi + env: COMMIT_LOG: ${{ env.COMMIT_LOG }} VERSION: ${{ env.VERSION }} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ce03636e..8983d640 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -19,6 +19,7 @@ + @@ -205,7 +206,7 @@ android:parentActivityName=".MainActivity" /> + android:windowSoftInputMode="adjustResize|stateVisible" /> () fun startTorrent() { if (torrentManager.isAvailable() && PrefManager.getVal(PrefName.TorrentEnabled)) { @@ -493,35 +496,28 @@ class MainActivity : AppCompatActivity() { val password = CharArray(16).apply { fill('0') } // Inflate the dialog layout - val dialogView = DialogUserAgentBinding.inflate(layoutInflater) - dialogView.userAgentTextBox.hint = "Password" - dialogView.subtitle.visibility = View.VISIBLE - dialogView.subtitle.text = getString(R.string.enter_password_to_decrypt_file) - - val dialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle("Enter Password") - .setView(dialogView.root) - .setPositiveButton("OK", null) - .setNegativeButton("Cancel") { dialog, _ -> + val dialogView = DialogUserAgentBinding.inflate(layoutInflater).apply { + userAgentTextBox.hint = "Password" + subtitle.visibility = View.VISIBLE + subtitle.text = getString(R.string.enter_password_to_decrypt_file) + } + customAlertDialog().apply { + setTitle("Enter Password") + setCustomView(dialogView.root) + setPosButton(R.string.yes) { + val editText = dialogView.userAgentTextBox + if (editText.text?.isNotBlank() == true) { + editText.text?.toString()?.trim()?.toCharArray(password) + callback(password) + } else { + toast("Password cannot be empty") + } + } + setNegButton(R.string.cancel) { password.fill('0') - dialog.dismiss() callback(null) } - .create() - - dialog.window?.setDimAmount(0.8f) - dialog.show() - - // Override the positive button here - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - val editText = dialog.findViewById(R.id.userAgentTextBox) - if (editText?.text?.isNotBlank() == true) { - editText.text?.toString()?.trim()?.toCharArray(password) - dialog.dismiss() - callback(password) - } else { - toast("Password cannot be empty") - } + show() } } diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt index 96ba5d75..3e24492c 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/Anilist.kt @@ -312,6 +312,7 @@ object Anilist { ) val remaining = json.headers["X-RateLimit-Remaining"]?.toIntOrNull() ?: -1 Logger.log("Remaining requests: $remaining") + println("Remaining requests: $remaining") if (json.code == 429) { val retry = json.headers["Retry-After"]?.toIntOrNull() ?: -1 val passedLimitReset = json.headers["X-RateLimit-Reset"]?.toLongOrNull() ?: 0 diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt index ee579e1b..f044349d 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt @@ -6,14 +6,14 @@ import ani.dantotsu.checkGenreTime import ani.dantotsu.checkId import ani.dantotsu.connections.anilist.Anilist.authorRoles import ani.dantotsu.connections.anilist.Anilist.executeQuery -import ani.dantotsu.connections.anilist.api.Activity import ani.dantotsu.connections.anilist.api.FeedResponse import ani.dantotsu.connections.anilist.api.FuzzyDate +import ani.dantotsu.connections.anilist.api.MediaEdge +import ani.dantotsu.connections.anilist.api.MediaList import ani.dantotsu.connections.anilist.api.NotificationResponse import ani.dantotsu.connections.anilist.api.Page import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.connections.anilist.api.ReplyResponse -import ani.dantotsu.connections.anilist.api.ToggleLike import ani.dantotsu.currContext import ani.dantotsu.isOnline import ani.dantotsu.logError @@ -28,6 +28,7 @@ import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream @@ -473,9 +474,7 @@ class AnilistQueries { val toShow: List = PrefManager.getVal(PrefName.HomeLayout) if (toShow.getOrNull(7) != true) return null - var query = """{""" - query += "Page1:${status(1)}Page2:${status(2)}" - query += """}""".trimEnd(',') + val query = """{Page1:${status(1)}Page2:${status(2)}}""" val response = executeQuery(query, show = true) val list = mutableListOf() val threeDaysAgo = Calendar.getInstance().apply { @@ -512,8 +511,9 @@ class AnilistQueries { } } - if (anilistActivities.isEmpty() && Anilist.token != null){ - anilistActivities.add(0, + if (anilistActivities.isEmpty() && Anilist.token != null) { + anilistActivities.add( + 0, User( Anilist.userid!!, Anilist.username!!, @@ -528,205 +528,176 @@ class AnilistQueries { } else return null } - suspend fun initHomePage(): Map> { + suspend fun initHomePage(): Map> { val removeList = PrefManager.getCustomVal("removeList", setOf()) + val hidePrivate = PrefManager.getVal(PrefName.HidePrivate) val removedMedia = ArrayList() val toShow: List = - PrefManager.getVal(PrefName.HomeLayout) // anime continue, anime fav, anime planned, manga continue, manga fav, manga planned, recommendations - var query = """{""" - if (toShow.getOrNull(0) == true) query += """currentAnime: ${ - continueMediaQuery( - "ANIME", - "CURRENT" - ) - }, repeatingAnime: ${continueMediaQuery("ANIME", "REPEATING")}""" - if (toShow.getOrNull(1) == true) query += """favoriteAnime: ${favMediaQuery(true, 1)}""" - if (toShow.getOrNull(2) == true) query += """plannedAnime: ${ - continueMediaQuery( - "ANIME", - "PLANNING" - ) - }""" - if (toShow.getOrNull(3) == true) query += """currentManga: ${ - continueMediaQuery( - "MANGA", - "CURRENT" - ) - }, repeatingManga: ${continueMediaQuery("MANGA", "REPEATING")}""" - if (toShow.getOrNull(4) == true) query += """favoriteManga: ${favMediaQuery(false, 1)}""" - if (toShow.getOrNull(5) == true) query += """plannedManga: ${ - continueMediaQuery( - "MANGA", - "PLANNING" - ) - }""" - if (toShow.getOrNull(6) == true) query += """recommendationQuery: ${recommendationQuery()}, recommendationPlannedQueryAnime: ${ - recommendationPlannedQuery( - "ANIME" - ) - }, recommendationPlannedQueryManga: ${recommendationPlannedQuery("MANGA")}""" - query += """}""".trimEnd(',') + PrefManager.getVal(PrefName.HomeLayout) // list of booleans for what to show + val queries = mutableListOf() + if (toShow.getOrNull(0) == true) { + queries.add("""currentAnime: ${continueMediaQuery("ANIME", "CURRENT")}""") + queries.add("""repeatingAnime: ${continueMediaQuery("ANIME", "REPEATING")}""") + } + if (toShow.getOrNull(1) == true) queries.add("""favoriteAnime: ${favMediaQuery(true, 1)}""") + if (toShow.getOrNull(2) == true) queries.add( + """plannedAnime: ${ + continueMediaQuery( + "ANIME", + "PLANNING" + ) + }""" + ) + if (toShow.getOrNull(3) == true) { + queries.add("""currentManga: ${continueMediaQuery("MANGA", "CURRENT")}""") + queries.add("""repeatingManga: ${continueMediaQuery("MANGA", "REPEATING")}""") + } + if (toShow.getOrNull(4) == true) queries.add( + """favoriteManga: ${ + favMediaQuery( + false, + 1 + ) + }""" + ) + if (toShow.getOrNull(5) == true) queries.add( + """plannedManga: ${ + continueMediaQuery( + "MANGA", + "PLANNING" + ) + }""" + ) + if (toShow.getOrNull(6) == true) { + queries.add("""recommendationQuery: ${recommendationQuery()}""") + queries.add("""recommendationPlannedQueryAnime: ${recommendationPlannedQuery("ANIME")}""") + queries.add("""recommendationPlannedQueryManga: ${recommendationPlannedQuery("MANGA")}""") + } + + val query = "{${queries.joinToString(",")}}" val response = executeQuery(query, show = true) - val returnMap = mutableMapOf>() - fun current(type: String) { + val returnMap = mutableMapOf>() + + fun processMedia( + type: String, + currentMedia: List?, + repeatingMedia: List? + ) { val subMap = mutableMapOf() val returnArray = arrayListOf() - val current = - if (type == "Anime") response?.data?.currentAnime else response?.data?.currentManga - val repeating = - if (type == "Anime") response?.data?.repeatingAnime else response?.data?.repeatingManga - current?.lists?.forEach { li -> - li.entries?.reversed()?.forEach { - val m = Media(it) - if (m.id !in removeList) { - m.cameFromContinue = true - subMap[m.id] = m - } else { - removedMedia.add(m) - } + + (currentMedia ?: emptyList()).forEach { entry -> + val media = Media(entry) + if (media.id !in removeList && (!hidePrivate || !media.isListPrivate)) { + media.cameFromContinue = true + subMap[media.id] = media + } else { + removedMedia.add(media) } } - repeating?.lists?.forEach { li -> - li.entries?.reversed()?.forEach { - val m = Media(it) - if (m.id !in removeList) { - m.cameFromContinue = true - subMap[m.id] = m - } else { - removedMedia.add(m) - } + (repeatingMedia ?: emptyList()).forEach { entry -> + val media = Media(entry) + if (media.id !in removeList && (!hidePrivate || !media.isListPrivate)) { + media.cameFromContinue = true + subMap[media.id] = media + } else { + removedMedia.add(media) } } - if (type != "Anime") { + @Suppress("UNCHECKED_CAST") + val list = PrefManager.getNullableCustomVal( + "continue${type}List", + listOf(), + List::class.java + ) as List + if (list.isNotEmpty()) { + list.reversed().forEach { id -> + subMap[id]?.let { returnArray.add(it) } + } + subMap.values.forEach { + if (!returnArray.contains(it)) returnArray.add(it) + } + } else { returnArray.addAll(subMap.values) - returnMap["current$type"] = returnArray - return } - @Suppress("UNCHECKED_CAST") - val list = PrefManager.getNullableCustomVal( - "continueAnimeList", - listOf(), - List::class.java - ) as List - if (list.isNotEmpty()) { - list.reversed().forEach { - if (subMap.containsKey(it)) returnArray.add(subMap[it]!!) - } - for (i in subMap) { - if (i.value !in returnArray) returnArray.add(i.value) - } - } else returnArray.addAll(subMap.values) returnMap["current$type"] = returnArray - } - fun planned(type: String) { - val subMap = mutableMapOf() - val returnArray = arrayListOf() - val current = - if (type == "Anime") response?.data?.plannedAnime else response?.data?.plannedManga - current?.lists?.forEach { li -> - li.entries?.reversed()?.forEach { - val m = Media(it) - if (m.id !in removeList) { - m.cameFromContinue = true - subMap[m.id] = m - } else { - removedMedia.add(m) - } - } - } - @Suppress("UNCHECKED_CAST") - val list = PrefManager.getNullableCustomVal( - "continueAnimeList", - listOf(), - List::class.java - ) as List - if (list.isNotEmpty()) { - list.reversed().forEach { - if (subMap.containsKey(it)) returnArray.add(subMap[it]!!) - } - for (i in subMap) { - if (i.value !in returnArray) returnArray.add(i.value) - } - } else returnArray.addAll(subMap.values) - returnMap["planned$type"] = returnArray - } + if (toShow.getOrNull(0) == true) processMedia( + "Anime", + response?.data?.currentAnime?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(), + response?.data?.repeatingAnime?.lists?.flatMap { it.entries ?: emptyList() }?.reversed() + ) + if (toShow.getOrNull(2) == true) processMedia( + "AnimePlanned", + response?.data?.plannedAnime?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(), + null + ) + if (toShow.getOrNull(3) == true) processMedia( + "Manga", + response?.data?.currentManga?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(), + response?.data?.repeatingManga?.lists?.flatMap { it.entries ?: emptyList() }?.reversed() + ) + if (toShow.getOrNull(5) == true) processMedia( + "MangaPlanned", + response?.data?.plannedManga?.lists?.flatMap { it.entries ?: emptyList() }?.reversed(), + null + ) - fun favorite(type: String) { - val favourites = - if (type == "Anime") response?.data?.favoriteAnime?.favourites else response?.data?.favoriteManga?.favourites - val apiMediaList = if (type == "Anime") favourites?.anime else favourites?.manga + fun processFavorites(type: String, favorites: List?) { val returnArray = arrayListOf() - apiMediaList?.edges?.forEach { - it.node?.let { i -> - val m = Media(i).apply { isFav = true } - if (m.id !in removeList) { - returnArray.add(m) + favorites?.forEach { edge -> + edge.node?.let { + val media = Media(it).apply { isFav = true } + if (media.id !in removeList && (!hidePrivate || !media.isListPrivate)) { + returnArray.add(media) } else { - removedMedia.add(m) + removedMedia.add(media) } } } returnMap["favorite$type"] = returnArray } - if (toShow.getOrNull(0) == true) { - current("Anime") - } - if (toShow.getOrNull(1) == true) { - favorite("Anime") - } - if (toShow.getOrNull(2) == true) { - planned("Anime") - } - if (toShow.getOrNull(3) == true) { - current("Manga") - } - if (toShow.getOrNull(4) == true) { - favorite("Manga") - } - if (toShow.getOrNull(5) == true) { - planned("Manga") - } + if (toShow.getOrNull(1) == true) processFavorites( + "Anime", + response?.data?.favoriteAnime?.favourites?.anime?.edges + ) + if (toShow.getOrNull(4) == true) processFavorites( + "Manga", + response?.data?.favoriteManga?.favourites?.manga?.edges + ) + if (toShow.getOrNull(6) == true) { val subMap = mutableMapOf() - response?.data?.recommendationQuery?.apply { - recommendations?.onEach { - val json = it.mediaRecommendation - if (json != null) { - val m = Media(json) - m.relation = json.type?.toString() - subMap[m.id] = m - } + response?.data?.recommendationQuery?.recommendations?.forEach { + it.mediaRecommendation?.let { json -> + val media = Media(json) + media.relation = json.type?.toString() + subMap[media.id] = media } } - response?.data?.recommendationPlannedQueryAnime?.apply { - lists?.forEach { li -> - li.entries?.forEach { - val m = Media(it) - if (m.status == "RELEASING" || m.status == "FINISHED") { - m.relation = it.media?.type?.toString() - subMap[m.id] = m - } - } + response?.data?.recommendationPlannedQueryAnime?.lists?.flatMap { + it.entries ?: emptyList() + }?.forEach { + val media = Media(it) + if (media.status in listOf("RELEASING", "FINISHED")) { + media.relation = it.media?.type?.toString() + subMap[media.id] = media } } - response?.data?.recommendationPlannedQueryManga?.apply { - lists?.forEach { li -> - li.entries?.forEach { - val m = Media(it) - if (m.status == "RELEASING" || m.status == "FINISHED") { - m.relation = it.media?.type?.toString() - subMap[m.id] = m - } - } + response?.data?.recommendationPlannedQueryManga?.lists?.flatMap { + it.entries ?: emptyList() + }?.forEach { + val media = Media(it) + if (media.status in listOf("RELEASING", "FINISHED")) { + media.relation = it.media?.type?.toString() + subMap[media.id] = media } } - val list = ArrayList(subMap.values.toList()) - list.sortByDescending { it.meanScore } + val list = ArrayList(subMap.values).apply { sortByDescending { it.meanScore } } returnMap["recommendations"] = list } @@ -1106,172 +1077,105 @@ query (${"$"}page: Int = 1, ${"$"}id: Int, ${"$"}type: MediaType, ${"$"}isAdult: return null } - private val onListAnime = - (if (PrefManager.getVal(PrefName.IncludeAnimeList)) "" else "onList:false").replace( - "\"", - "" - ) - private val isAdult = - (if (PrefManager.getVal(PrefName.AdultOnly)) "isAdult:true" else "").replace("\"", "") + private fun mediaList(media1: Page?): ArrayList { + val combinedList = arrayListOf() + media1?.media?.mapTo(combinedList) { Media(it) } + return combinedList + } + + private fun getPreference(pref: PrefName): Boolean = PrefManager.getVal(pref) + + private fun buildQueryString( + sort: String, + type: String, + format: String? = null, + country: String? = null + ): String { + val includeList = when { + type == "ANIME" && !getPreference(PrefName.IncludeAnimeList) -> "onList:false" + type == "MANGA" && !getPreference(PrefName.IncludeMangaList) -> "onList:false" + else -> "" + } + val isAdult = if (getPreference(PrefName.AdultOnly)) "isAdult:true" else "" + val formatFilter = format?.let { "format:$it, " } ?: "" + val countryFilter = country?.let { "countryOfOrigin:$it, " } ?: "" + + return buildString { + append("""Page(page:1,perPage:50){pageInfo{hasNextPage total}media(sort:$sort, type:$type, $formatFilter $countryFilter $includeList $isAdult){id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}""") + } + } private fun recentAnimeUpdates(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}airingSchedules(airingAt_greater:0 airingAt_lesser:${System.currentTimeMillis() / 1000 - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}""" - } - - private fun trendingMovies(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: ANIME, format: MOVIE, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun topRatedAnime(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun mostFavAnime(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: ANIME, $onListAnime, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - suspend fun loadAnimeList(): Map> { - val list = mutableMapOf>() - fun query(): String { - return """{ - recentUpdates:${recentAnimeUpdates(1)} - recentUpdates2:${recentAnimeUpdates(2)} - trendingMovies:${trendingMovies(1)} - trendingMovies2:${trendingMovies(2)} - topRated:${topRatedAnime(1)} - topRated2:${topRatedAnime(2)} - mostFav:${mostFavAnime(1)} - mostFav2:${mostFavAnime(2)} - }""".trimIndent() + val currentTime = System.currentTimeMillis() / 1000 + return buildString { + append("""Page(page:$page,perPage:50){pageInfo{hasNextPage total}airingSchedules(airingAt_greater:0 airingAt_lesser:${currentTime - 10000} sort:TIME_DESC){episode airingAt media{id idMal status chapters episodes nextAiringEpisode{episode} isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large} title{english romaji userPreferred} mediaListEntry{progress private score(format:POINT_100) status}}}}""") } - executeQuery(query(), force = true)?.data?.apply { - val listOnly: Boolean = PrefManager.getVal(PrefName.RecentlyListOnly) - val adultOnly: Boolean = PrefManager.getVal(PrefName.AdultOnly) - val idArr = mutableListOf() - list["recentUpdates"] = recentUpdates?.airingSchedules?.mapNotNull { i -> - i.media?.let { - if (!idArr.contains(it.id)) - if (!listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true) { - idArr.add(it.id) - Media(it) - } else if (!listOnly && !adultOnly && (it.countryOfOrigin == "JP" && it.isAdult == false)) { - idArr.add(it.id) - Media(it) - } else if ((listOnly && it.mediaListEntry != null)) { - idArr.add(it.id) - Media(it) - } else null - else null + } + + private fun queryAnimeList(): String { + return buildString { + append("""{recentUpdates:${recentAnimeUpdates(1)} recentUpdates2:${recentAnimeUpdates(2)} trendingMovies:${buildQueryString("POPULARITY_DESC", "ANIME", "MOVIE")} topRated:${buildQueryString("SCORE_DESC", "ANIME")} mostFav:${buildQueryString("FAVOURITES_DESC", "ANIME")}}""") + } + } + + private fun queryMangaList(): String { + return buildString { + append("""{trendingManga:${buildQueryString("POPULARITY_DESC", "MANGA", country = "JP")} trendingManhwa:${buildQueryString("POPULARITY_DESC", "MANGA", country = "KR")} trendingNovel:${buildQueryString("POPULARITY_DESC", "MANGA", format = "NOVEL", country = "JP")} topRated:${buildQueryString("SCORE_DESC", "MANGA")} mostFav:${buildQueryString("FAVOURITES_DESC", "MANGA")}}""") + } + } + + suspend fun loadAnimeList(): Map> = coroutineScope { + val list = mutableMapOf>() + + fun filterRecentUpdates(page: Page?): ArrayList { + val listOnly = getPreference(PrefName.RecentlyListOnly) + val adultOnly = getPreference(PrefName.AdultOnly) + val idArr = mutableSetOf() + return page?.airingSchedules?.mapNotNull { i -> + i.media?.takeIf { !idArr.contains(it.id) }?.let { + val shouldAdd = when { + !listOnly && it.countryOfOrigin == "JP" && adultOnly && it.isAdult == true -> true + !listOnly && !adultOnly && it.countryOfOrigin == "JP" && it.isAdult == false -> true + listOnly && it.mediaListEntry != null -> true + else -> false + } + if (shouldAdd) { + idArr.add(it.id) + Media(it) + } else null } }?.toCollection(ArrayList()) ?: arrayListOf() - - list["trendingMovies"] = - trendingMovies?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["topRated"] = - topRated?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["mostFav"] = - mostFav?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - - list["recentUpdates"]?.addAll(recentUpdates2?.airingSchedules?.mapNotNull { i -> - i.media?.let { - if (!idArr.contains(it.id)) - if (!listOnly && it.countryOfOrigin == "JP" && Anilist.adult && adultOnly && it.isAdult == true) { - idArr.add(it.id) - Media(it) - } else if (!listOnly && !adultOnly && (it.countryOfOrigin == "JP" && it.isAdult == false)) { - idArr.add(it.id) - Media(it) - } else if ((listOnly && it.mediaListEntry != null)) { - idArr.add(it.id) - Media(it) - } else null - else null - } - }?.toCollection(ArrayList()) ?: arrayListOf()) - list["trendingMovies"]?.addAll(trendingMovies2?.media?.map { Media(it) } - ?.toCollection(ArrayList()) ?: arrayListOf()) - list["topRated"]?.addAll( - topRated2?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - ) - list["mostFav"]?.addAll( - mostFav2?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - ) } - return list + + val animeList = async { executeQuery(queryAnimeList(), force = true) } + + animeList.await()?.data?.apply { + list["recentUpdates"] = filterRecentUpdates(recentUpdates) + list["trendingMovies"] = mediaList(trendingMovies) + list["topRated"] = mediaList(topRated) + list["mostFav"] = mediaList(mostFav) + } + + list } - private val onListManga = - (if (PrefManager.getVal(PrefName.IncludeMangaList)) "" else "onList:false").replace( - "\"", - "" - ) - - private fun trendingManga(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA,countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun trendingManhwa(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, countryOfOrigin:KR, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun trendingNovel(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:POPULARITY_DESC, type: MANGA, format: NOVEL, countryOfOrigin:JP, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun topRatedManga(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort: SCORE_DESC, type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - private fun mostFavManga(page: Int): String { - return """Page(page:$page,perPage:50){pageInfo{hasNextPage total}media(sort:FAVOURITES_DESC,type: MANGA, $onListManga, $isAdult){id idMal status chapters episodes nextAiringEpisode{episode}isAdult type meanScore isFavourite format bannerImage countryOfOrigin coverImage{large}title{english romaji userPreferred}mediaListEntry{progress private score(format:POINT_100)status}}}""" - } - - suspend fun loadMangaList(): Map> { + suspend fun loadMangaList(): Map> = coroutineScope { val list = mutableMapOf>() - fun query(): String { - return """{ - trendingManga:${trendingManga(1)} - trendingManga2:${trendingManga(2)} - trendingManhwa:${trendingManhwa(1)} - trendingManhwa2:${trendingManhwa(2)} - trendingNovel:${trendingNovel(1)} - trendingNovel2:${trendingNovel(2)} - topRated:${topRatedManga(1)} - topRated2:${topRatedManga(2)} - mostFav:${mostFavManga(1)} - mostFav2:${mostFavManga(2)} - }""".trimIndent() + + val mangaList = async { executeQuery(queryMangaList(), force = true) } + + mangaList.await()?.data?.apply { + list["trendingManga"] = mediaList(trendingManga) + list["trendingManhwa"] = mediaList(trendingManhwa) + list["trendingNovel"] = mediaList(trendingNovel) + list["topRated"] = mediaList(topRated) + list["mostFav"] = mediaList(mostFav) } - executeQuery(query(), force = true)?.data?.apply { - list["trendingManga"] = - trendingManga?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["trendingManhwa"] = - trendingManhwa?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["trendingNovel"] = - trendingNovel?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["topRated"] = - topRated?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - list["mostFav"] = - mostFav?.media?.map { Media(it) }?.toCollection(ArrayList()) ?: arrayListOf() - - list["trendingManga"]?.addAll( - trendingManga2?.media?.map { Media(it) }?.toList() ?: arrayListOf() - ) - list["trendingManhwa"]?.addAll( - trendingManhwa2?.media?.map { Media(it) }?.toList() ?: arrayListOf() - ) - list["trendingNovel"]?.addAll( - trendingNovel2?.media?.map { Media(it) }?.toList() ?: arrayListOf() - ) - list["topRated"]?.addAll(topRated2?.media?.map { Media(it) }?.toList() ?: arrayListOf()) - list["mostFav"]?.addAll(mostFav2?.media?.map { Media(it) }?.toList() ?: arrayListOf()) - } - - - return list + list } + suspend fun recentlyUpdated( greater: Long = 0, lesser: Long = System.currentTimeMillis() / 1000 - 10000 @@ -1672,10 +1576,10 @@ Page(page:$page,perPage:50) { resetNotification: Boolean = true, type: Boolean? = null ): NotificationResponse? { - val type_in = "type_in:[AIRING,MEDIA_MERGE,MEDIA_DELETION,MEDIA_DATA_CHANGE]" + val typeIn = "type_in:[AIRING,MEDIA_MERGE,MEDIA_DELETION,MEDIA_DATA_CHANGE]" val reset = if (resetNotification) "true" else "false" val res = executeQuery( - """{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset , ${if (type == true) type_in else ""}){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""", + """{User(id:$id){unreadNotificationCount}Page(page:$page,perPage:$ITEMS_PER_PAGE){pageInfo{currentPage,hasNextPage}notifications(resetNotificationCount:$reset , ${if (type == true) typeIn else ""}){__typename...on AiringNotification{id,type,animeId,episode,contexts,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}},}...on FollowingNotification{id,userId,type,context,createdAt,user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMessageNotification{id,userId,type,activityId,context,createdAt,message{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityMentionNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplySubscribedNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ActivityReplyLikeNotification{id,userId,type,activityId,context,createdAt,activity{__typename}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentMentionNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentReplyNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentSubscribedNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadCommentLikeNotification{id,userId,type,commentId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on ThreadLikeNotification{id,userId,type,threadId,context,createdAt,thread{id}comment{id}user{id,name,bannerImage,avatar{medium,large,}}}...on RelatedMediaAdditionNotification{id,type,context,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDataChangeNotification{id,type,mediaId,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaMergeNotification{id,type,mediaId,deletedMediaTitles,context,reason,createdAt,media{id,title{romaji,english,native,userPreferred}bannerImage,coverImage{medium,large}}}...on MediaDeletionNotification{id,type,deletedMediaTitle,context,reason,createdAt,}}}}""", force = true ) if (res != null && resetNotification) { diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt index e75383a1..f1db7ff6 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistViewModel.kt @@ -91,17 +91,16 @@ class AnilistHomeViewModel : ViewModel() { fun getHidden(): LiveData> = hidden - @Suppress("UNCHECKED_CAST") suspend fun initHomePage() { val res = Anilist.query.initHomePage() - res["currentAnime"]?.let { animeContinue.postValue(it as ArrayList?) } - res["favoriteAnime"]?.let { animeFav.postValue(it as ArrayList?) } - res["plannedAnime"]?.let { animePlanned.postValue(it as ArrayList?) } - res["currentManga"]?.let { mangaContinue.postValue(it as ArrayList?) } - res["favoriteManga"]?.let { mangaFav.postValue(it as ArrayList?) } - res["plannedManga"]?.let { mangaPlanned.postValue(it as ArrayList?) } - res["recommendations"]?.let { recommendation.postValue(it as ArrayList?) } - res["hidden"]?.let { hidden.postValue(it as ArrayList?) } + res["currentAnime"]?.let { animeContinue.postValue(it) } + res["favoriteAnime"]?.let { animeFav.postValue(it) } + res["currentAnimePlanned"]?.let { animePlanned.postValue(it) } + res["currentManga"]?.let { mangaContinue.postValue(it) } + res["favoriteManga"]?.let { mangaFav.postValue(it) } + res["currentMangaPlanned"]?.let { mangaPlanned.postValue(it) } + res["recommendations"]?.let { recommendation.postValue(it) } + res["hidden"]?.let { hidden.postValue(it) } } suspend fun loadMain(context: FragmentActivity) { diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt index e6635b9a..e909a3bc 100644 --- a/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt +++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Data.kt @@ -163,13 +163,9 @@ class Query { @Serializable data class Data( @SerialName("recentUpdates") val recentUpdates: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("recentUpdates2") val recentUpdates2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingMovies") val trendingMovies: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("trendingMovies2") val trendingMovies2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("topRated2") val topRated2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("mostFav2") val mostFav2: ani.dantotsu.connections.anilist.api.Page?, ) } @@ -181,15 +177,10 @@ class Query { @Serializable data class Data( @SerialName("trendingManga") val trendingManga: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("trendingManga2") val trendingManga2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingManhwa") val trendingManhwa: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("trendingManhwa2") val trendingManhwa2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("trendingNovel") val trendingNovel: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("trendingNovel2") val trendingNovel2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("topRated") val topRated: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("topRated2") val topRated2: ani.dantotsu.connections.anilist.api.Page?, @SerialName("mostFav") val mostFav: ani.dantotsu.connections.anilist.api.Page?, - @SerialName("mostFav2") val mostFav2: ani.dantotsu.connections.anilist.api.Page?, ) } diff --git a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt index 3bcdffe1..4f3876fe 100644 --- a/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/anime/OfflineAnimeFragment.kt @@ -49,6 +49,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.anggrayudi.storage.file.openInputStream import com.google.android.material.card.MaterialCardView import com.google.android.material.imageview.ShapeableImageView @@ -203,25 +204,22 @@ class OfflineAnimeFragment : Fragment(), OfflineAnimeSearchListener { val type: MediaType = MediaType.ANIME // Alert dialog to confirm deletion - val builder = - androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup) - builder.setTitle("Delete ${item.title}?") - builder.setMessage("Are you sure you want to delete ${item.title}?") - builder.setPositiveButton("Yes") { _, _ -> - downloadManager.removeMedia(item.title, type) - val mediaIds = - PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values - ?: emptySet() - if (mediaIds.isEmpty()) { - snackString("No media found") // if this happens, terrible things have happened + requireContext().customAlertDialog().apply { + setTitle("Delete ${item.title}?") + setMessage("Are you sure you want to delete ${item.title}?") + setPosButton(R.string.yes) { + downloadManager.removeMedia(item.title, type) + val mediaIds = PrefManager.getAnimeDownloadPreferences().all?.filter { it.key.contains(item.title) }?.values ?: emptySet() + if (mediaIds.isEmpty()) { + snackString("No media found") // if this happens, terrible things have happened + } + getDownloads() } - getDownloads() + setNegButton(R.string.no) { + // Do nothing + } + show() } - builder.setNegativeButton("No") { _, _ -> - // Do nothing - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) true } } diff --git a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt index 4452a2df..ed3cb02c 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/MangaDownloaderService.kt @@ -32,6 +32,7 @@ import ani.dantotsu.media.manga.MangaReadFragment.Companion.ACTION_DOWNLOAD_STAR import ani.dantotsu.media.manga.MangaReadFragment.Companion.EXTRA_CHAPTER_NUMBER import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.NumberConverter.Companion.ofLength import com.anggrayudi.storage.file.deleteRecursively import com.anggrayudi.storage.file.forceDelete import com.anggrayudi.storage.file.openOutputStream @@ -235,7 +236,7 @@ class MangaDownloaderService : Service() { } if (bitmap != null) { - saveToDisk("$index.jpg", outputDir, bitmap) + saveToDisk("${index.ofLength(3)}.jpg", outputDir, bitmap) } farthest++ diff --git a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt index 6d88918e..dcb462e8 100644 --- a/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/download/manga/OfflineMangaFragment.kt @@ -46,6 +46,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.anggrayudi.storage.file.openInputStream import com.google.android.material.card.MaterialCardView import com.google.android.material.imageview.ShapeableImageView @@ -201,19 +202,15 @@ class OfflineMangaFragment : Fragment(), OfflineMangaSearchListener { MediaType.NOVEL } // Alert dialog to confirm deletion - val builder = - androidx.appcompat.app.AlertDialog.Builder(requireContext(), R.style.MyPopup) - builder.setTitle("Delete ${item.title}?") - builder.setMessage("Are you sure you want to delete ${item.title}?") - builder.setPositiveButton("Yes") { _, _ -> - downloadManager.removeMedia(item.title, type) - getDownloads() - } - builder.setNegativeButton("No") { _, _ -> - // Do nothing - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) + requireContext().customAlertDialog().apply { + setTitle("Delete ${item.title}?") + setMessage("Are you sure you want to delete ${item.title}?") + setPosButton(R.string.yes) { + downloadManager.removeMedia(item.title, type) + getDownloads() + } + setNegButton(R.string.no) + }.show() true } } diff --git a/app/src/main/java/ani/dantotsu/download/video/Helper.kt b/app/src/main/java/ani/dantotsu/download/video/Helper.kt index b207d634..ab8dd73d 100644 --- a/app/src/main/java/ani/dantotsu/download/video/Helper.kt +++ b/app/src/main/java/ani/dantotsu/download/video/Helper.kt @@ -3,7 +3,6 @@ package ani.dantotsu.download.video import android.Manifest import android.annotation.SuppressLint import android.app.Activity -import android.app.AlertDialog import android.content.Context import android.content.Intent import android.content.pm.PackageManager @@ -29,10 +28,10 @@ import ani.dantotsu.download.anime.AnimeDownloaderService import ani.dantotsu.download.anime.AnimeServiceDataSingleton import ani.dantotsu.media.Media import ani.dantotsu.media.MediaType -import ani.dantotsu.parsers.Subtitle import ani.dantotsu.parsers.Video import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import eu.kanade.tachiyomi.network.NetworkHelper import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -72,19 +71,19 @@ object Helper { episodeImage ) - val downloadsManger = Injekt.get() - val downloadCheck = downloadsManger + val downloadsManager = Injekt.get() + val downloadCheck = downloadsManager .queryDownload(title, episode, MediaType.ANIME) if (downloadCheck) { - AlertDialog.Builder(context, R.style.MyPopup) - .setTitle("Download Exists") - .setMessage("A download for this episode already exists. Do you want to overwrite it?") - .setPositiveButton("Yes") { _, _ -> + context.customAlertDialog().apply { + setTitle("Download Exists") + setMessage("A download for this episode already exists. Do you want to overwrite it?") + setPosButton(R.string.yes) { PrefManager.getAnimeDownloadPreferences().edit() .remove(animeDownloadTask.getTaskName()) .apply() - downloadsManger.removeDownload( + downloadsManager.removeDownload( DownloadedType( title, episode, @@ -99,8 +98,9 @@ object Helper { } } } - .setNegativeButton("No") { _, _ -> } - .show() + setNegButton(R.string.no) + show() + } } else { AnimeServiceDataSingleton.downloadQueue.offer(animeDownloadTask) if (!AnimeServiceDataSingleton.isServiceRunning) { diff --git a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt index f9a9f596..87713885 100644 --- a/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/AnimeFragment.kt @@ -38,6 +38,7 @@ import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -289,15 +290,20 @@ class AnimeFragment : Fragment() { } } } - model.loaded = true - model.loadTrending(1) - model.loadAll() + } + model.loaded = true + val loadTrending = async(Dispatchers.IO) { model.loadTrending(1) } + val loadAll = async(Dispatchers.IO) { model.loadAll() } + val loadPopular = async(Dispatchers.IO) { model.loadPopular( - "ANIME", sort = Anilist.sortBy[1], onList = PrefManager.getVal( - PrefName.PopularAnimeList - ) + "ANIME", + sort = Anilist.sortBy[1], + onList = PrefManager.getVal(PrefName.PopularAnimeList) ) } + loadTrending.await() + loadAll.await() + loadPopular.await() live.postValue(false) _binding?.animeRefresh?.isRefreshing = false running = false diff --git a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt index a66506d9..dfd39e6a 100644 --- a/app/src/main/java/ani/dantotsu/home/HomeFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/HomeFragment.kt @@ -50,6 +50,7 @@ import ani.dantotsu.statusBarHeight import ani.dantotsu.util.Logger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlin.math.max @@ -457,54 +458,56 @@ class HomeFragment : Fragment() { var running = false val live = Refresh.activity.getOrPut(1) { MutableLiveData(true) } - live.observe(viewLifecycleOwner) - { - if (!running && it) { + live.observe(viewLifecycleOwner) { shouldRefresh -> + if (!running && shouldRefresh) { running = true scope.launch { withContext(Dispatchers.IO) { - //Get userData First - Anilist.userid = - PrefManager.getNullableVal(PrefName.AnilistUserId, null) - ?.toIntOrNull() + // Get user data first + Anilist.userid = PrefManager.getNullableVal(PrefName.AnilistUserId, null)?.toIntOrNull() if (Anilist.userid == null) { - getUserId(requireContext()) { - load() - } - } else { - CoroutineScope(Dispatchers.IO).launch { + withContext(Dispatchers.Main) { getUserId(requireContext()) { load() } } + } else { + getUserId(requireContext()) { + load() + } } model.loaded = true - CoroutineScope(Dispatchers.IO).launch { - model.setListImages() - } - CoroutineScope(Dispatchers.IO).launch { - model.initUserStatus() - } - var empty = true - val homeLayoutShow: List = - PrefManager.getVal(PrefName.HomeLayout) - model.initHomePage() - (array.indices).forEach { i -> + model.setListImages() + } + + var empty = true + val homeLayoutShow: List = PrefManager.getVal(PrefName.HomeLayout) + + withContext(Dispatchers.Main) { + homeLayoutShow.indices.forEach { i -> if (homeLayoutShow.elementAt(i)) { empty = false - } else withContext(Dispatchers.Main) { + } else { containers[i].visibility = View.GONE } } - model.empty.postValue(empty) } + + val initHomePage = async(Dispatchers.IO) { model.initHomePage() } + val initUserStatus = async(Dispatchers.IO) { model.initUserStatus() } + initHomePage.await() + initUserStatus.await() + + withContext(Dispatchers.Main) { + model.empty.postValue(empty) + binding.homeHiddenItemsContainer.visibility = View.GONE + } + live.postValue(false) _binding?.homeRefresh?.isRefreshing = false running = false } - binding.homeHiddenItemsContainer.visibility = View.GONE } - } } diff --git a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt index 5f89464e..13fde1e5 100644 --- a/app/src/main/java/ani/dantotsu/home/LoginFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/LoginFragment.kt @@ -12,12 +12,14 @@ import androidx.documentfile.provider.DocumentFile import androidx.fragment.app.Fragment import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist +import ani.dantotsu.databinding.DialogUserAgentBinding import ani.dantotsu.databinding.FragmentLoginBinding import ani.dantotsu.openLinkInBrowser import ani.dantotsu.settings.saving.internal.PreferenceKeystore import ani.dantotsu.settings.saving.internal.PreferencePackager import ani.dantotsu.toast import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.google.android.material.textfield.TextInputEditText class LoginFragment : Fragment() { @@ -94,38 +96,31 @@ class LoginFragment : Fragment() { val password = CharArray(16).apply { fill('0') } // Inflate the dialog layout - val dialogView = - LayoutInflater.from(requireActivity()).inflate(R.layout.dialog_user_agent, null) - dialogView.findViewById(R.id.userAgentTextBox)?.hint = "Password" - val subtitleTextView = dialogView.findViewById(R.id.subtitle) - subtitleTextView?.visibility = View.VISIBLE - subtitleTextView?.text = "Enter your password to decrypt the file" + val dialogView = DialogUserAgentBinding.inflate(layoutInflater).apply { + userAgentTextBox.hint = "Password" + subtitle.visibility = View.VISIBLE + subtitle.text = getString(R.string.enter_password_to_decrypt_file) + } - val dialog = AlertDialog.Builder(requireActivity(), R.style.MyPopup) - .setTitle("Enter Password") - .setView(dialogView) - .setPositiveButton("OK", null) - .setNegativeButton("Cancel") { dialog, _ -> + requireActivity().customAlertDialog().apply { + setTitle("Enter Password") + setCustomView(dialogView.root) + setPosButton(R.string.ok){ + val editText = dialogView.userAgentTextBox + if (editText.text?.isNotBlank() == true) { + editText.text?.toString()?.trim()?.toCharArray(password) + callback(password) + } else { + toast("Password cannot be empty") + } + } + setNegButton(R.string.cancel) { password.fill('0') - dialog.dismiss() callback(null) } - .create() + }.show() - dialog.window?.setDimAmount(0.8f) - dialog.show() - // Override the positive button here - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - val editText = dialog.findViewById(R.id.userAgentTextBox) - if (editText?.text?.isNotBlank() == true) { - editText.text?.toString()?.trim()?.toCharArray(password) - dialog.dismiss() - callback(password) - } else { - toast("Password cannot be empty") - } - } } private fun restartApp() { diff --git a/app/src/main/java/ani/dantotsu/home/MangaFragment.kt b/app/src/main/java/ani/dantotsu/home/MangaFragment.kt index c878c584..0ed19510 100644 --- a/app/src/main/java/ani/dantotsu/home/MangaFragment.kt +++ b/app/src/main/java/ani/dantotsu/home/MangaFragment.kt @@ -35,6 +35,7 @@ import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -274,15 +275,22 @@ class MangaFragment : Fragment() { } } } - model.loaded = true - model.loadTrending() - model.loadAll() + } + model.loaded = true + val loadTrending = async(Dispatchers.IO) { model.loadTrending() } + val loadAll = async(Dispatchers.IO) { model.loadAll() } + val loadPopular = async(Dispatchers.IO) { model.loadPopular( - "MANGA", sort = Anilist.sortBy[1], onList = PrefManager.getVal( - PrefName.PopularMangaList - ) + "MANGA", + sort = Anilist.sortBy[1], + onList = PrefManager.getVal(PrefName.PopularAnimeList) ) } + + loadTrending.await() + loadAll.await() + loadPopular.await() + live.postValue(false) _binding?.mangaRefresh?.isRefreshing = false running = false diff --git a/app/src/main/java/ani/dantotsu/home/status/StatusActivity.kt b/app/src/main/java/ani/dantotsu/home/status/StatusActivity.kt index 7fc0e78b..ced8b2d8 100644 --- a/app/src/main/java/ani/dantotsu/home/status/StatusActivity.kt +++ b/app/src/main/java/ani/dantotsu/home/status/StatusActivity.kt @@ -49,7 +49,10 @@ class StatusActivity : AppCompatActivity(), StoriesCallback { if (activity.getOrNull(position) != null) { val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity ) val startIndex = if ( startFrom > 0) startFrom else 0 - binding.stories.setStoriesList(activity[position].activity, this, startIndex + 1) + binding.stories.setStoriesList( + activityList = activity[position].activity, + startIndex = startIndex + 1 + ) } else { Logger.log("index out of bounds for position $position of size ${activity.size}") finish() @@ -92,7 +95,7 @@ class StatusActivity : AppCompatActivity(), StoriesCallback { val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity ) val startIndex= if ( startFrom > 0) startFrom else 0 binding.stories.startAnimation(slideOutLeft) - binding.stories.setStoriesList(activity[position].activity, this, startIndex + 1) + binding.stories.setStoriesList(activity[position].activity, startIndex + 1) binding.stories.startAnimation(slideInRight) } else { finish() @@ -107,7 +110,7 @@ class StatusActivity : AppCompatActivity(), StoriesCallback { val startFrom = findFirstNonMatch(watchedActivity, activity[position].activity ) val startIndex = if ( startFrom > 0) startFrom else 0 binding.stories.startAnimation(slideOutRight) - binding.stories.setStoriesList(activity[position].activity, this, startIndex + 1) + binding.stories.setStoriesList(activity[position].activity,startIndex + 1) binding.stories.startAnimation(slideInLeft) } else { finish() diff --git a/app/src/main/java/ani/dantotsu/home/status/Stories.kt b/app/src/main/java/ani/dantotsu/home/status/Stories.kt index d6522844..a141a5d6 100644 --- a/app/src/main/java/ani/dantotsu/home/status/Stories.kt +++ b/app/src/main/java/ani/dantotsu/home/status/Stories.kt @@ -5,7 +5,6 @@ import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.util.AttributeSet -import android.view.Gravity import android.view.LayoutInflater import android.view.MotionEvent import android.view.View @@ -16,7 +15,6 @@ import androidx.core.app.ActivityOptionsCompat import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.isVisible -import androidx.core.widget.NestedScrollView import androidx.fragment.app.FragmentActivity import ani.dantotsu.R import ani.dantotsu.blurImage @@ -32,6 +30,7 @@ import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.User import ani.dantotsu.profile.UsersDialogFragment import ani.dantotsu.profile.activity.ActivityItemBuilder +import ani.dantotsu.profile.activity.RepliesBottomDialog import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString @@ -50,7 +49,6 @@ import kotlin.math.abs class Stories @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr), View.OnTouchListener { - private lateinit var activity: FragmentActivity private lateinit var binding: FragmentStatusBinding private lateinit var activityList: List private lateinit var storiesListener: StoriesCallback @@ -81,10 +79,9 @@ class Stories @JvmOverloads constructor( fun setStoriesList( - activityList: List, activity: FragmentActivity, startIndex: Int = 1 + activityList: List, startIndex: Int = 1 ) { this.activityList = activityList - this.activity = activity this.storyIndex = startIndex addLoadingViews(activityList) } @@ -369,7 +366,9 @@ class Stories @JvmOverloads constructor( if ( story.status?.contains("completed") == false && !story.status.contains("plans") && - !story.status.contains("repeating") + !story.status.contains("repeating")&& + !story.status.contains("paused")&& + !story.status.contains("dropped") ) { "of ${story.media?.title?.userPreferred}" } else { @@ -390,7 +389,7 @@ class Stories @JvmOverloads constructor( story.media?.id ), ActivityOptionsCompat.makeSceneTransitionAnimation( - activity, + (it.context as FragmentActivity), binding.coverImage, ViewCompat.getTransitionName(binding.coverImage)!! ).toBundle() @@ -428,7 +427,7 @@ class Stories @JvmOverloads constructor( binding.activityReplies.setColorFilter(ContextCompat.getColor(context, R.color.bg_opp)) binding.activityRepliesContainer.setOnClickListener { RepliesBottomDialog.newInstance(story.id) - .show(activity.supportFragmentManager, "replies") + .show((it.context as FragmentActivity).supportFragmentManager, "replies") } binding.activityLike.setColorFilter(if (story.isLiked == true) likeColor else notLikeColor) binding.activityLikeCount.text = story.likeCount.toString() @@ -436,10 +435,9 @@ class Stories @JvmOverloads constructor( like() } binding.activityLikeContainer.setOnLongClickListener { - val context = activity UsersDialogFragment().apply { userList(userList) - show(context.supportFragmentManager, "dialog") + show((it.context as FragmentActivity).supportFragmentManager, "dialog") } true } diff --git a/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt b/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt index 66cec7e4..230f3a4e 100644 --- a/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/CalendarActivity.kt @@ -30,6 +30,7 @@ class CalendarActivity : AppCompatActivity() { private lateinit var binding: ActivityListBinding private val scope = lifecycleScope private var selectedTabIdx = 1 + private var showOnlyLibrary = false private val model: OtherDetailsViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { @@ -38,8 +39,6 @@ class CalendarActivity : AppCompatActivity() { ThemeManager(this).applyTheme() binding = ActivityListBinding.inflate(layoutInflater) - - val primaryColor = getThemeColor(com.google.android.material.R.attr.colorSurface) val primaryTextColor = getThemeColor(com.google.android.material.R.attr.colorPrimary) val secondaryTextColor = getThemeColor(com.google.android.material.R.attr.colorOutline) @@ -79,6 +78,17 @@ class CalendarActivity : AppCompatActivity() { override fun onTabReselected(tab: TabLayout.Tab?) {} }) + binding.listed.setOnClickListener { + showOnlyLibrary = !showOnlyLibrary + binding.listed.setImageResource( + if (showOnlyLibrary) R.drawable.ic_round_collections_bookmark_24 + else R.drawable.ic_round_library_books_24 + ) + scope.launch { + model.loadCalendar(showOnlyLibrary) + } + } + model.getCalendar().observe(this) { if (it != null) { binding.listProgressBar.visibility = View.GONE @@ -97,11 +107,10 @@ class CalendarActivity : AppCompatActivity() { live.observe(this) { if (it) { scope.launch { - withContext(Dispatchers.IO) { model.loadCalendar() } + withContext(Dispatchers.IO) { model.loadCalendar(showOnlyLibrary) } live.postValue(false) } } } - } } diff --git a/app/src/main/java/ani/dantotsu/media/OtherDetailsViewModel.kt b/app/src/main/java/ani/dantotsu/media/OtherDetailsViewModel.kt index 0be0fc22..a086a765 100644 --- a/app/src/main/java/ani/dantotsu/media/OtherDetailsViewModel.kt +++ b/app/src/main/java/ani/dantotsu/media/OtherDetailsViewModel.kt @@ -26,25 +26,50 @@ class OtherDetailsViewModel : ViewModel() { if (author.value == null) author.postValue(Anilist.query.getAuthorDetails(m)) } + private var cachedAllCalendarData: Map>? = null + private var cachedLibraryCalendarData: Map>? = null private val calendar: MutableLiveData>> = MutableLiveData(null) fun getCalendar(): LiveData>> = calendar - suspend fun loadCalendar() { - val curr = System.currentTimeMillis() / 1000 - val res = Anilist.query.recentlyUpdated(curr - 86400, curr + (86400 * 6)) - val df = DateFormat.getDateInstance(DateFormat.FULL) - val map = mutableMapOf>() - val idMap = mutableMapOf>() - res?.forEach { - val v = it.relation?.split(",")?.map { i -> i.toLong() }!! - val dateInfo = df.format(Date(v[1] * 1000)) - val list = map.getOrPut(dateInfo) { mutableListOf() } - val idList = idMap.getOrPut(dateInfo) { mutableListOf() } - it.relation = "Episode ${v[0]}" - if (!idList.contains(it.id)) { - idList.add(it.id) - list.add(it) + suspend fun loadCalendar(showOnlyLibrary: Boolean = false) { + if (cachedAllCalendarData == null || cachedLibraryCalendarData == null) { + val curr = System.currentTimeMillis() / 1000 + val res = Anilist.query.recentlyUpdated(curr - 86400, curr + (86400 * 6)) + val df = DateFormat.getDateInstance(DateFormat.FULL) + val allMap = mutableMapOf>() + val libraryMap = mutableMapOf>() + val idMap = mutableMapOf>() + + val userId = Anilist.userid ?: 0 + val userLibrary = Anilist.query.getMediaLists(true, userId) + val libraryMediaIds = userLibrary.flatMap { it.value }.map { it.id } + + res.forEach { + val v = it.relation?.split(",")?.map { i -> i.toLong() }!! + val dateInfo = df.format(Date(v[1] * 1000)) + val list = allMap.getOrPut(dateInfo) { mutableListOf() } + val libraryList = if (libraryMediaIds.contains(it.id)) { + libraryMap.getOrPut(dateInfo) { mutableListOf() } + } else { + null + } + val idList = idMap.getOrPut(dateInfo) { mutableListOf() } + it.relation = "Episode ${v[0]}" + if (!idList.contains(it.id)) { + idList.add(it.id) + list.add(it) + libraryList?.add(it) + } } + + cachedAllCalendarData = allMap + cachedLibraryCalendarData = libraryMap } - calendar.postValue(map) + + val cacheToUse: Map> = if (showOnlyLibrary) { + cachedLibraryCalendarData ?: emptyMap() + } else { + cachedAllCalendarData ?: emptyMap() + } + calendar.postValue(cacheToUse) } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt b/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt index ecf3692c..21f0b683 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchAdapter.kt @@ -183,6 +183,12 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri binding.searchByImage.setOnClickListener { activity.startActivity(Intent(activity, ImageSearchActivity::class.java)) } + binding.clearHistory.setOnClickListener { + it.startAnimation(fadeOutAnimation()) + it.visibility = View.GONE + searchHistoryAdapter.clearHistory() + } + updateClearHistoryVisibility() fun searchTitle() { activity.result.apply { search = @@ -300,11 +306,17 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri } binding.searchResultLayout.visibility = View.VISIBLE + binding.clearHistory.visibility = View.GONE binding.searchHistoryList.visibility = View.GONE binding.searchByImage.visibility = View.GONE } } + private fun updateClearHistoryVisibility() { + binding.clearHistory.visibility = + if (searchHistoryAdapter.itemCount > 0) View.VISIBLE else View.GONE + } + private fun fadeInAnimation(): Animation { return AlphaAnimation(0f, 1f).apply { duration = 150 @@ -375,4 +387,3 @@ class SearchAdapter(private val activity: SearchActivity, private val type: Stri override fun getItemCount(): Int = chips.size } } - diff --git a/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt b/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt index f1519e2b..4e2988e3 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt @@ -49,6 +49,12 @@ class SearchHistoryAdapter(private val type: String, private val searchClicked: PrefManager.setVal(historyType, searchHistory) } + fun clearHistory() { + searchHistory?.clear() + PrefManager.setVal(historyType, searchHistory) + submitList(searchHistory?.toList()) + } + override fun onCreateViewHolder( parent: ViewGroup, viewType: Int diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt index a2406a21..e1c77312 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchAdapter.kt @@ -8,7 +8,6 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.ImageButton import android.widget.LinearLayout -import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.startActivity import androidx.core.view.isGone @@ -19,7 +18,7 @@ import ani.dantotsu.FileUrl import ani.dantotsu.R import ani.dantotsu.currActivity import ani.dantotsu.databinding.DialogLayoutBinding -import ani.dantotsu.databinding.ItemAnimeWatchBinding +import ani.dantotsu.databinding.ItemMediaSourceBinding import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.displayTimer import ani.dantotsu.isOnline @@ -33,12 +32,14 @@ import ani.dantotsu.others.LanguageMapper import ani.dantotsu.others.webview.CookieCatcher import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.DynamicAnimeParser +import ani.dantotsu.parsers.OfflineAnimeParser import ani.dantotsu.parsers.WatchSources import ani.dantotsu.px import ani.dantotsu.settings.FAQActivity import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.toast +import ani.dantotsu.util.customAlertDialog import com.google.android.material.chip.Chip import eu.kanade.tachiyomi.animesource.online.AnimeHttpSource import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK @@ -54,16 +55,13 @@ class AnimeWatchAdapter( ) : RecyclerView.Adapter() { private var autoSelect = true var subscribe: MediaDetailsActivity.PopImageButton? = null - private var _binding: ItemAnimeWatchBinding? = null + private var _binding: ItemMediaSourceBinding? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val bind = ItemAnimeWatchBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val bind = ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(bind) } - private var nestedDialog: AlertDialog? = null - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { val binding = holder.binding _binding = binding @@ -75,7 +73,7 @@ class AnimeWatchAdapter( null ) } - //Youtube + // Youtube if (media.anime?.youtube != null && PrefManager.getVal(PrefName.ShowYtButton)) { binding.animeSourceYT.visibility = View.VISIBLE binding.animeSourceYT.setOnClickListener { @@ -89,7 +87,7 @@ class AnimeWatchAdapter( R.string.subbed ) - //PreferDub + // PreferDub var changing = false binding.animeSourceDubbed.setOnCheckedChangeListener { _, isChecked -> binding.animeSourceDubbedText.text = @@ -99,8 +97,8 @@ class AnimeWatchAdapter( if (!changing) fragment.onDubClicked(isChecked) } - //Wrong Title - binding.animeSourceSearch.setOnClickListener { + // Wrong Title + binding.mediaSourceSearch.setOnClickListener { SourceSearchDialogFragment().show( fragment.requireActivity().supportFragmentManager, null @@ -108,37 +106,37 @@ class AnimeWatchAdapter( } val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode) - binding.animeSourceNameContainer.isGone = offline - binding.animeSourceSettings.isGone = offline - binding.animeSourceSearch.isGone = offline - binding.animeSourceTitle.isGone = offline + binding.mediaSourceNameContainer.isGone = offline + binding.mediaSourceSettings.isGone = offline + binding.mediaSourceSearch.isGone = offline + binding.mediaSourceTitle.isGone = offline - //Source Selection + // Source Selection var source = media.selected!!.sourceIndex.let { if (it >= watchSources.names.size) 0 else it } setLanguageList(media.selected!!.langIndex, source) if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { - binding.animeSource.setText(watchSources.names[source]) + binding.mediaSource.setText(watchSources.names[source]) watchSources[source].apply { this.selectDub = media.selected!!.preferDub - binding.animeSourceTitle.text = showUserText - showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + binding.mediaSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } } binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately() } } - binding.animeSource.setAdapter( + binding.mediaSource.setAdapter( ArrayAdapter( fragment.requireContext(), R.layout.item_dropdown, watchSources.names ) ) - binding.animeSourceTitle.isSelected = true - binding.animeSource.setOnItemClickListener { _, _, i, _ -> + binding.mediaSourceTitle.isSelected = true + binding.mediaSource.setOnItemClickListener { _, _, i, _ -> fragment.onSourceChange(i).apply { - binding.animeSourceTitle.text = showUserText - showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + binding.mediaSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } } changing = true binding.animeSourceDubbed.isChecked = selectDub changing = false @@ -150,15 +148,15 @@ class AnimeWatchAdapter( fragment.loadEpisodes(i, false) } - binding.animeSourceLanguage.setOnItemClickListener { _, _, i, _ -> + binding.mediaSourceLanguage.setOnItemClickListener { _, _, i, _ -> // Check if 'extension' and 'selected' properties exist and are accessible (watchSources[source] as? DynamicAnimeParser)?.let { ext -> ext.sourceLanguage = i fragment.onLangChange(i) fragment.onSourceChange(media.selected!!.sourceIndex).apply { - binding.animeSourceTitle.text = showUserText + binding.mediaSourceTitle.text = showUserText showUserTextListener = - { MainScope().launch { binding.animeSourceTitle.text = it } } + { MainScope().launch { binding.mediaSourceTitle.text = it } } changing = true binding.animeSourceDubbed.isChecked = selectDub changing = false @@ -170,19 +168,19 @@ class AnimeWatchAdapter( } ?: run { } } - //settings - binding.animeSourceSettings.setOnClickListener { + // Settings + binding.mediaSourceSettings.setOnClickListener { (watchSources[source] as? DynamicAnimeParser)?.let { ext -> fragment.openSettings(ext.extension) } } - //Icons + // Icons - //subscribe + // Subscribe subscribe = MediaDetailsActivity.PopImageButton( fragment.lifecycleScope, - binding.animeSourceSubscribe, + binding.mediaSourceSubscribe, R.drawable.ic_round_notifications_active_24, R.drawable.ic_round_notifications_none_24, R.color.bg_opp, @@ -190,117 +188,115 @@ class AnimeWatchAdapter( fragment.subscribed, true ) { - fragment.onNotificationPressed(it, binding.animeSource.text.toString()) + fragment.onNotificationPressed(it, binding.mediaSource.text.toString()) } subscribeButton(false) - binding.animeSourceSubscribe.setOnLongClickListener { + binding.mediaSourceSubscribe.setOnLongClickListener { openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK) } - //Nested Button - binding.animeNestedButton.setOnClickListener { - val dialogView = - LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null) - val dialogBinding = DialogLayoutBinding.bind(dialogView) - var refresh = false - var run = false - var reversed = media.selected!!.recyclerReversed - var style = - media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView) - dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f - dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" - dialogBinding.animeSourceTop.setOnClickListener { - reversed = !reversed - dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f - dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" - run = true - } - //Grids - var selected = when (style) { - 0 -> dialogBinding.animeSourceList - 1 -> dialogBinding.animeSourceGrid - 2 -> dialogBinding.animeSourceCompact - else -> dialogBinding.animeSourceList - } - when (style) { - 0 -> dialogBinding.layoutText.setText(R.string.list) - 1 -> dialogBinding.layoutText.setText(R.string.grid) - 2 -> dialogBinding.layoutText.setText(R.string.compact) - else -> dialogBinding.animeSourceList - } - selected.alpha = 1f - fun selected(it: ImageButton) { - selected.alpha = 0.33f - selected = it - selected.alpha = 1f - } - dialogBinding.animeSourceList.setOnClickListener { - selected(it as ImageButton) - style = 0 - dialogBinding.layoutText.setText(R.string.list) - run = true - } - dialogBinding.animeSourceGrid.setOnClickListener { - selected(it as ImageButton) - style = 1 - dialogBinding.layoutText.setText(R.string.grid) - run = true - } - dialogBinding.animeSourceCompact.setOnClickListener { - selected(it as ImageButton) - style = 2 - dialogBinding.layoutText.setText(R.string.compact) - run = true - } - dialogBinding.animeWebviewContainer.setOnClickListener { - if (!WebViewUtil.supportsWebView(fragment.requireContext())) { - toast(R.string.webview_not_installed) + // Nested Button + binding.mediaNestedButton.setOnClickListener { + val dialogBinding = DialogLayoutBinding.inflate(fragment.layoutInflater) + dialogBinding.apply { + var refresh = false + var run = false + var reversed = media.selected!!.recyclerReversed + var style = + media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.AnimeDefaultView) + + mediaSourceTop.rotation = if (reversed) -90f else 90f + sortText.text = if (reversed) "Down to Up" else "Up to Down" + mediaSourceTop.setOnClickListener { + reversed = !reversed + mediaSourceTop.rotation = if (reversed) -90f else 90f + sortText.text = if (reversed) "Down to Up" else "Up to Down" + run = true } - //start CookieCatcher activity - if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { - val sourceAHH = watchSources[source] as? DynamicAnimeParser - val sourceHttp = - sourceAHH?.extension?.sources?.firstOrNull() as? AnimeHttpSource - val url = sourceHttp?.baseUrl - url?.let { - refresh = true - val headersMap = try { - sourceHttp.headers.toMultimap() - .mapValues { it.value.getOrNull(0) ?: "" } - } catch (e: Exception) { - emptyMap() + // Grids + var selected = when (style) { + 0 -> mediaSourceList + 1 -> mediaSourceGrid + 2 -> mediaSourceCompact + else -> mediaSourceList + } + when (style) { + 0 -> layoutText.setText(R.string.list) + 1 -> layoutText.setText(R.string.grid) + 2 -> layoutText.setText(R.string.compact) + else -> mediaSourceList + } + selected.alpha = 1f + fun selected(it: ImageButton) { + selected.alpha = 0.33f + selected = it + selected.alpha = 1f + } + mediaSourceList.setOnClickListener { + selected(it as ImageButton) + style = 0 + layoutText.setText(R.string.list) + run = true + } + mediaSourceGrid.setOnClickListener { + selected(it as ImageButton) + style = 1 + layoutText.setText(R.string.grid) + run = true + } + mediaSourceCompact.setOnClickListener { + selected(it as ImageButton) + style = 2 + layoutText.setText(R.string.compact) + run = true + } + mediaWebviewContainer.setOnClickListener { + if (!WebViewUtil.supportsWebView(fragment.requireContext())) { + toast(R.string.webview_not_installed) + } + // Start CookieCatcher activity + if (watchSources.names.isNotEmpty() && source in 0 until watchSources.names.size) { + val sourceAHH = watchSources[source] as? DynamicAnimeParser + val sourceHttp = + sourceAHH?.extension?.sources?.firstOrNull() as? AnimeHttpSource + val url = sourceHttp?.baseUrl + url?.let { + refresh = true + val headersMap = try { + sourceHttp.headers.toMultimap() + .mapValues { it.value.getOrNull(0) ?: "" } + } catch (e: Exception) { + emptyMap() + } + val intent = + Intent(fragment.requireContext(), CookieCatcher::class.java) + .putExtra("url", url) + .putExtra("headers", headersMap as HashMap) + startActivity(fragment.requireContext(), intent, null) } - val intent = Intent(fragment.requireContext(), CookieCatcher::class.java) - .putExtra("url", url) - .putExtra("headers", headersMap as HashMap) - startActivity(fragment.requireContext(), intent, null) } } + + // Hidden + mangaScanlatorContainer.visibility = View.GONE + animeDownloadContainer.visibility = View.GONE + fragment.requireContext().customAlertDialog().apply { + setTitle("Options") + setCustomView(dialogBinding.root) + setPosButton("OK") { + if (run) fragment.onIconPressed(style, reversed) + if (refresh) fragment.loadEpisodes(source, true) + } + setNegButton("Cancel") { + if (refresh) fragment.loadEpisodes(source, true) + } + show() + } } - - //hidden - dialogBinding.animeScanlatorContainer.visibility = View.GONE - dialogBinding.animeDownloadContainer.visibility = View.GONE - - nestedDialog = AlertDialog.Builder(fragment.requireContext(), R.style.MyPopup) - .setTitle("Options") - .setView(dialogView) - .setPositiveButton("OK") { _, _ -> - if (run) fragment.onIconPressed(style, reversed) - if (refresh) fragment.loadEpisodes(source, true) - } - .setNegativeButton("Cancel") { _, _ -> - if (refresh) fragment.loadEpisodes(source, true) - } - .setOnCancelListener { - if (refresh) fragment.loadEpisodes(source, true) - } - .create() - nestedDialog?.show() } - //Episode Handling + // Episode Handling handleEpisodes() } @@ -308,7 +304,7 @@ class AnimeWatchAdapter( subscribe?.enabled(enabled) } - //Chips + // Chips fun updateChips(limit: Int, names: Array, arr: Array, selected: Int = 0) { val binding = _binding if (binding != null) { @@ -319,13 +315,13 @@ class AnimeWatchAdapter( val chip = ItemChipBinding.inflate( LayoutInflater.from(fragment.context), - binding.animeSourceChipGroup, + binding.mediaSourceChipGroup, false ).root chip.isCheckable = true fun selected() { chip.isChecked = true - binding.animeWatchChipScroll.smoothScrollTo( + binding.mediaWatchChipScroll.smoothScrollTo( (chip.left - screenWidth / 2) + (chip.width / 2), 0 ) @@ -344,14 +340,14 @@ class AnimeWatchAdapter( selected() fragment.onChipClicked(position, limit * (position), last - 1) } - binding.animeSourceChipGroup.addView(chip) + binding.mediaSourceChipGroup.addView(chip) if (selected == position) { selected() select = chip } } if (select != null) - binding.animeWatchChipScroll.apply { + binding.mediaWatchChipScroll.apply { post { scrollTo( (select.left - screenWidth / 2) + (select.width / 2), @@ -363,7 +359,7 @@ class AnimeWatchAdapter( } fun clearChips() { - _binding?.animeSourceChipGroup?.removeAllViews() + _binding?.mediaSourceChipGroup?.removeAllViews() } fun handleEpisodes() { @@ -379,15 +375,15 @@ class AnimeWatchAdapter( var continueEp = (if (anilistEp > appEp) anilistEp else appEp).toString() if (episodes.contains(continueEp)) { - binding.animeSourceContinue.visibility = View.VISIBLE + binding.sourceContinue.visibility = View.VISIBLE handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, continueEp ) - if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > PrefManager.getVal( + if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > PrefManager.getVal( PrefName.WatchPercentage ) ) { @@ -395,9 +391,9 @@ class AnimeWatchAdapter( if (e != -1 && e + 1 < episodes.size) { continueEp = episodes[e + 1] handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, continueEp ) @@ -407,51 +403,63 @@ class AnimeWatchAdapter( val cleanedTitle = ep.title?.let { MediaNameAdapter.removeEpisodeNumber(it) } - binding.itemEpisodeImage.loadImage( + binding.itemMediaImage.loadImage( ep.thumb ?: FileUrl[media.banner ?: media.cover], 0 ) if (ep.filler) binding.itemEpisodeFillerView.visibility = View.VISIBLE - binding.animeSourceContinueText.text = + binding.mediaSourceContinueText.text = currActivity()!!.getString( R.string.continue_episode, ep.number, if (ep.filler) currActivity()!!.getString(R.string.filler_tag) else "", cleanedTitle ) - binding.animeSourceContinue.setOnClickListener { + binding.sourceContinue.setOnClickListener { fragment.onEpisodeClick(continueEp) } if (fragment.continueEp) { if ( - (binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams) + (binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams) .weight < PrefManager.getVal(PrefName.WatchPercentage) ) { - binding.animeSourceContinue.performClick() + binding.sourceContinue.performClick() fragment.continueEp = false } } } else { - binding.animeSourceContinue.visibility = View.GONE + binding.sourceContinue.visibility = View.GONE } - binding.animeSourceProgressBar.visibility = View.GONE + binding.sourceProgressBar.visibility = View.GONE val sourceFound = media.anime.episodes!!.isNotEmpty() - binding.animeSourceNotFound.isGone = sourceFound + val isDownloadedSource = watchSources[media.selected!!.sourceIndex] is OfflineAnimeParser + + if (isDownloadedSource) { + binding.sourceNotFound.text = if (sourceFound) { + currActivity()!!.getString(R.string.source_not_found) + } else { + currActivity()!!.getString(R.string.download_not_found) + } + } else { + binding.sourceNotFound.text = currActivity()!!.getString(R.string.source_not_found) + } + + binding.sourceNotFound.isGone = sourceFound binding.faqbutton.isGone = sourceFound if (!sourceFound && PrefManager.getVal(PrefName.SearchSources) && autoSelect) { - if (binding.animeSource.adapter.count > media.selected!!.sourceIndex + 1) { + if (binding.mediaSource.adapter.count > media.selected!!.sourceIndex + 1) { val nextIndex = media.selected!!.sourceIndex + 1 - binding.animeSource.setText( - binding.animeSource.adapter + binding.mediaSource.setText( + binding.mediaSource.adapter .getItem(nextIndex).toString(), false ) fragment.onSourceChange(nextIndex).apply { - binding.animeSourceTitle.text = showUserText + binding.mediaSourceTitle.text = showUserText showUserTextListener = - { MainScope().launch { binding.animeSourceTitle.text = it } } + { MainScope().launch { binding.mediaSourceTitle.text = it } } binding.animeSourceDubbed.isChecked = selectDub binding.animeSourceDubbedCont.isVisible = isDubAvailableSeparately() setLanguageList(0, nextIndex) @@ -460,13 +468,13 @@ class AnimeWatchAdapter( fragment.loadEpisodes(nextIndex, false) } } - binding.animeSource.setOnClickListener { autoSelect = false } + binding.mediaSource.setOnClickListener { autoSelect = false } } else { - binding.animeSourceContinue.visibility = View.GONE - binding.animeSourceNotFound.visibility = View.GONE + binding.sourceContinue.visibility = View.GONE + binding.sourceNotFound.visibility = View.GONE binding.faqbutton.visibility = View.GONE clearChips() - binding.animeSourceProgressBar.visibility = View.VISIBLE + binding.sourceProgressBar.visibility = View.VISIBLE } } } @@ -480,9 +488,9 @@ class AnimeWatchAdapter( ext.sourceLanguage = lang } try { - binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang) + binding?.mediaSourceLanguage?.setText(parser.extension.sources[lang].lang) } catch (e: IndexOutOfBoundsException) { - binding?.animeSourceLanguage?.setText( + binding?.mediaSourceLanguage?.setText( parser.extension.sources.firstOrNull()?.lang ?: "Unknown" ) } @@ -493,9 +501,9 @@ class AnimeWatchAdapter( ) val items = adapter.count - binding?.animeSourceLanguageContainer?.visibility = + binding?.mediaSourceLanguageContainer?.visibility = if (items > 1) View.VISIBLE else View.GONE - binding?.animeSourceLanguage?.setAdapter(adapter) + binding?.mediaSourceLanguage?.setAdapter(adapter) } } @@ -503,7 +511,7 @@ class AnimeWatchAdapter( override fun getItemCount(): Int = 1 - inner class ViewHolder(val binding: ItemAnimeWatchBinding) : + inner class ViewHolder(val binding: ItemMediaSourceBinding) : RecyclerView.ViewHolder(binding.root) { init { displayTimer(media, binding.animeSourceContainer) diff --git a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt index ecd8ee73..847d8755 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/AnimeWatchFragment.kt @@ -32,7 +32,7 @@ import ani.dantotsu.FileUrl import ani.dantotsu.R import ani.dantotsu.addons.download.DownloadAddonManager import ani.dantotsu.connections.anilist.api.MediaStreamingEpisode -import ani.dantotsu.databinding.FragmentAnimeWatchBinding +import ani.dantotsu.databinding.FragmentMediaSourceBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager.Companion.compareName @@ -63,6 +63,7 @@ import ani.dantotsu.toast import ani.dantotsu.util.Logger import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess +import ani.dantotsu.util.customAlertDialog import com.anggrayudi.storage.file.extension import com.google.android.material.appbar.AppBarLayout import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource @@ -80,7 +81,7 @@ import kotlin.math.max import kotlin.math.roundToInt class AnimeWatchFragment : Fragment() { - private var _binding: FragmentAnimeWatchBinding? = null + private var _binding: FragmentMediaSourceBinding? = null private val binding get() = _binding!! private val model: MediaDetailsViewModel by activityViewModels() @@ -107,7 +108,7 @@ class AnimeWatchFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = FragmentAnimeWatchBinding.inflate(inflater, container, false) + _binding = FragmentMediaSourceBinding.inflate(inflater, container, false) return _binding?.root } @@ -128,7 +129,7 @@ class AnimeWatchFragment : Fragment() { ) - binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight) + binding.mediaSourceRecycler.updatePadding(bottom = binding.mediaSourceRecycler.paddingBottom + navBarHeight) screenWidth = resources.displayMetrics.widthPixels.dp var maxGridSize = (screenWidth / 100f).roundToInt() @@ -152,13 +153,13 @@ class AnimeWatchFragment : Fragment() { } } - binding.animeSourceRecycler.layoutManager = gridLayoutManager + binding.mediaSourceRecycler.layoutManager = gridLayoutManager binding.ScrollTop.setOnClickListener { - binding.animeSourceRecycler.scrollToPosition(10) - binding.animeSourceRecycler.smoothScrollToPosition(0) + binding.mediaSourceRecycler.scrollToPosition(10) + binding.mediaSourceRecycler.smoothScrollToPosition(0) } - binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { + binding.mediaSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) @@ -172,7 +173,7 @@ class AnimeWatchFragment : Fragment() { } }) model.scrolledToTop.observe(viewLifecycleOwner) { - if (it) binding.animeSourceRecycler.scrollToPosition(0) + if (it) binding.mediaSourceRecycler.scrollToPosition(0) } continueEp = model.continueMedia ?: false @@ -205,7 +206,7 @@ class AnimeWatchFragment : Fragment() { offlineMode = offlineMode ) - binding.animeSourceRecycler.adapter = + binding.mediaSourceRecycler.adapter = ConcatAdapter(headerAdapter, episodeAdapter) lifecycleScope.launch(Dispatchers.IO) { @@ -266,7 +267,7 @@ class AnimeWatchFragment : Fragment() { } media.anime?.episodes = episodes - //CHIP GROUP + // CHIP GROUP val total = episodes.size val divisions = total.toDouble() / 10 start = 0 @@ -396,20 +397,18 @@ class AnimeWatchFragment : Fragment() { if (allSettings.size > 1) { val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) }.toTypedArray() - val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) - .setTitle("Select a Source") - .setSingleChoiceItems(names, -1) { dialog, which -> + requireContext() + .customAlertDialog() + .apply { + setTitle("Select a Source") + singleChoiceItems(names) { which -> selectedSetting = allSettings[which] itemSelected = true - dialog.dismiss() - - // Move the fragment transaction here requireActivity().runOnUiThread { - val fragment = - AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) { - changeUIVisibility(true) - loadEpisodes(media.selected!!.sourceIndex, true) - } + val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) { + changeUIVisibility(true) + loadEpisodes(media.selected!!.sourceIndex, true) + } parentFragmentManager.beginTransaction() .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) .replace(R.id.fragmentExtensionsContainer, fragment) @@ -417,13 +416,13 @@ class AnimeWatchFragment : Fragment() { .commit() } } - .setOnDismissListener { + onDismiss { if (!itemSelected) { changeUIVisibility(true) } } - .show() - dialog.window?.setDimAmount(0.8f) + show() + } } else { // If there's only one setting, proceed with the fragment transaction requireActivity().runOnUiThread { @@ -432,11 +431,12 @@ class AnimeWatchFragment : Fragment() { changeUIVisibility(true) loadEpisodes(media.selected!!.sourceIndex, true) } - parentFragmentManager.beginTransaction() - .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) - .replace(R.id.fragmentExtensionsContainer, fragment) - .addToBackStack(null) - .commit() + parentFragmentManager.beginTransaction().apply { + setCustomAnimations(R.anim.slide_up, R.anim.slide_down) + replace(R.id.fragmentExtensionsContainer, fragment) + addToBackStack(null) + commit() + } } } @@ -635,7 +635,7 @@ class AnimeWatchFragment : Fragment() { private fun reload() { val selected = model.loadSelected(media) - //Find latest episode for subscription + // Find latest episode for subscription selected.latest = media.anime?.episodes?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f selected.latest = @@ -679,14 +679,14 @@ class AnimeWatchFragment : Fragment() { override fun onResume() { super.onResume() binding.mediaInfoProgressBar.visibility = progress - binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state) + binding.mediaSourceRecycler.layoutManager?.onRestoreInstanceState(state) requireActivity().setNavigationTheme() } override fun onPause() { super.onPause() - state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState() + state = binding.mediaSourceRecycler.layoutManager?.onSaveInstanceState() } companion object { diff --git a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt index 14be291a..c0ac4352 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/EpisodeAdapters.kt @@ -23,6 +23,7 @@ import ani.dantotsu.media.MediaNameAdapter import ani.dantotsu.media.MediaType import ani.dantotsu.setAnimation import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.util.customAlertDialog import com.bumptech.glide.Glide import com.bumptech.glide.load.model.GlideUrl import kotlinx.coroutines.delay @@ -106,8 +107,8 @@ class EpisodeAdapter( val thumb = ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null } - Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0) - .into(binding.itemEpisodeImage) + Glide.with(binding.itemMediaImage).load(thumb ?: media.cover).override(400, 0) + .into(binding.itemMediaImage) binding.itemEpisodeNumber.text = ep.number binding.itemEpisodeTitle.text = if (ep.number == title) "Episode $title" else title @@ -140,9 +141,9 @@ class EpisodeAdapter( } handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, ep.number ) @@ -154,8 +155,8 @@ class EpisodeAdapter( val thumb = ep.thumb?.let { if (it.url.isNotEmpty()) GlideUrl(it.url) { it.headers } else null } - Glide.with(binding.itemEpisodeImage).load(thumb ?: media.cover).override(400, 0) - .into(binding.itemEpisodeImage) + Glide.with(binding.itemMediaImage).load(thumb ?: media.cover).override(400, 0) + .into(binding.itemMediaImage) binding.itemEpisodeNumber.text = ep.number binding.itemEpisodeTitle.text = title @@ -183,9 +184,9 @@ class EpisodeAdapter( binding.itemEpisodeViewed.visibility = View.GONE } handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, ep.number ) @@ -208,9 +209,9 @@ class EpisodeAdapter( } } handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, ep.number ) @@ -318,16 +319,14 @@ class EpisodeAdapter( fragment.onAnimeEpisodeStopDownloadClick(episodeNumber) return@setOnClickListener } else if (downloadedEpisodes.contains(episodeNumber)) { - val builder = AlertDialog.Builder(currContext(), R.style.MyPopup) - builder.setTitle("Delete Episode") - builder.setMessage("Are you sure you want to delete Episode ${episodeNumber}?") - builder.setPositiveButton("Yes") { _, _ -> - fragment.onAnimeEpisodeRemoveDownloadClick(episodeNumber) - } - builder.setNegativeButton("No") { _, _ -> - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) + binding.root.context.customAlertDialog().apply { + setTitle("Delete Episode") + setMessage("Are you sure you want to delete Episode $episodeNumber?") + setPosButton(R.string.yes) { + fragment.onAnimeEpisodeRemoveDownloadClick(episodeNumber) + } + setNegButton(R.string.no) + }.show() return@setOnClickListener } else { fragment.onAnimeEpisodeDownloadClick(episodeNumber) diff --git a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt index e93267e2..febd93ec 100644 --- a/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/anime/SelectorDialogFragment.kt @@ -444,15 +444,12 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { if (subtitles.isNotEmpty()) { val subtitleNames = subtitles.map { it.language } var subtitleToDownload: Subtitle? = null - val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.download_subtitle) - .setSingleChoiceItems( - subtitleNames.toTypedArray(), - -1 - ) { _, which -> + requireActivity().customAlertDialog().apply { + setTitle(R.string.download_subtitle) + singleChoiceItems(subtitleNames.toTypedArray()) {which -> subtitleToDownload = subtitles[which] } - .setPositiveButton(R.string.download) { dialog, _ -> + setPosButton(R.string.download) { scope.launch { if (subtitleToDownload != null) { SubtitleDownloader.downloadSubtitle( @@ -466,13 +463,9 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { ) } } - dialog.dismiss() } - .setNegativeButton(R.string.cancel) { dialog, _ -> - dialog.dismiss() - } - .show() - alertDialog.window?.setDimAmount(0.8f) + setNegButton(R.string.cancel) {} + }.show() } else { snackString(R.string.no_subtitles_available) } @@ -576,65 +569,63 @@ class SelectorDialogFragment : BottomSheetDialogFragment() { if (audioTracks.isNotEmpty()) { val audioNamesArray = audioTracks.toTypedArray() val checkedItems = BooleanArray(audioNamesArray.size) { false } - val alertDialog = AlertDialog.Builder(currContext, R.style.MyPopup) - .setTitle(R.string.download_audio_tracks) - .setMultiChoiceItems(audioNamesArray, checkedItems) { _, which, isChecked -> - val audioPair = Pair(extractor.audioTracks[which].url, extractor.audioTracks[which].lang) - if (isChecked) { - selectedAudioTracks.add(audioPair) - } else { - selectedAudioTracks.remove(audioPair) + + currContext.customAlertDialog().apply{ // ToTest + setTitle(R.string.download_audio_tracks) + multiChoiceItems(audioNamesArray, checkedItems) { + it.forEachIndexed { index, isChecked -> + val audioPair = Pair(extractor.audioTracks[index].url, extractor.audioTracks[index].lang) + if (isChecked) { + selectedAudioTracks.add(audioPair) + } else { + selectedAudioTracks.remove(audioPair) + } } } - .setPositiveButton(R.string.download) { _, _ -> - dialog?.dismiss() + setPosButton(R.string.download) { go() } - .setNegativeButton(R.string.skip) { dialog, _ -> + setNegButton(R.string.skip) { selectedAudioTracks = mutableListOf() go() - dialog.dismiss() } - .setNeutralButton(R.string.cancel) { dialog, _ -> + setNeutralButton(R.string.cancel) { selectedAudioTracks = mutableListOf() - dialog.dismiss() } - .show() - alertDialog.window?.setDimAmount(0.8f) + show() + } } else { go() } } - if (subtitles.isNotEmpty()) { + if (subtitles.isNotEmpty()) { // ToTest val subtitleNamesArray = subtitleNames.toTypedArray() val checkedItems = BooleanArray(subtitleNamesArray.size) { false } - val alertDialog = AlertDialog.Builder(currContext, R.style.MyPopup) - .setTitle(R.string.download_subtitle) - .setMultiChoiceItems(subtitleNamesArray, checkedItems) { _, which, isChecked -> - val subtitlePair = Pair(subtitles[which].file.url, subtitles[which].language) - if (isChecked) { - selectedSubtitles.add(subtitlePair) - } else { - selectedSubtitles.remove(subtitlePair) + currContext.customAlertDialog().apply { + setTitle(R.string.download_subtitle) + multiChoiceItems(subtitleNamesArray, checkedItems) { + it.forEachIndexed { index, isChecked -> + val subtitlePair = Pair(subtitles[index].file.url, subtitles[index].language) + if (isChecked) { + selectedSubtitles.add(subtitlePair) + } else { + selectedSubtitles.remove(subtitlePair) + } } } - .setPositiveButton(R.string.download) { _, _ -> - dialog?.dismiss() + setPosButton(R.string.download) { checkAudioTracks() } - .setNegativeButton(R.string.skip) { dialog, _ -> + setNegButton(R.string.skip) { selectedSubtitles = mutableListOf() checkAudioTracks() - dialog.dismiss() } - .setNeutralButton(R.string.cancel) { dialog, _ -> + setNeutralButton(R.string.cancel) { selectedSubtitles = mutableListOf() - dialog.dismiss() } - .show() - alertDialog.window?.setDimAmount(0.8f) - + show() + } } else { checkAudioTracks() } diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt index 6c552090..0a4bed33 100644 --- a/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt +++ b/app/src/main/java/ani/dantotsu/media/comments/CommentItem.kt @@ -21,6 +21,7 @@ import ani.dantotsu.setAnimation import ani.dantotsu.snackString import ani.dantotsu.util.ColorEditor.Companion.adjustColorForContrast import ani.dantotsu.util.ColorEditor.Companion.getContrastRatio +import ani.dantotsu.util.customAlertDialog import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.Section import com.xwray.groupie.viewbinding.BindableItem @@ -385,19 +386,14 @@ class CommentItem( * @param callback the callback to call when the user clicks yes */ private fun dialogBuilder(title: String, message: String, callback: () -> Unit) { - val alertDialog = - android.app.AlertDialog.Builder(commentsFragment.activity, R.style.MyPopup) - .setTitle(title) - .setMessage(message) - .setPositiveButton("Yes") { dialog, _ -> - callback() - dialog.dismiss() - } - .setNegativeButton("No") { dialog, _ -> - dialog.dismiss() - } - val dialog = alertDialog.show() - dialog?.window?.setDimAmount(0.8f) + commentsFragment.activity.customAlertDialog().apply { + setTitle(title) + setMessage(message) + setPosButton("Yes") { + callback() + } + setNegButton("No") {} + }.show() } private val usernameColors: Array = arrayOf( diff --git a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt b/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt index ff861dbe..de1c76b8 100644 --- a/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/comments/CommentsFragment.kt @@ -25,6 +25,7 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.comments.Comment import ani.dantotsu.connections.comments.CommentResponse import ani.dantotsu.connections.comments.CommentsAPI +import ani.dantotsu.databinding.DialogEdittextBinding import ani.dantotsu.databinding.FragmentCommentsBinding import ani.dantotsu.loadImage import ani.dantotsu.media.MediaDetailsActivity @@ -34,6 +35,7 @@ import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.toast import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.Section import io.noties.markwon.editor.MarkwonEditor @@ -162,33 +164,26 @@ class CommentsFragment : Fragment() { } binding.commentFilter.setOnClickListener { - val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup) - .setTitle("Enter a chapter/episode number tag") - .setView(R.layout.dialog_edittext) - .setPositiveButton("OK") { dialog, _ -> - val editText = - (dialog as AlertDialog).findViewById(R.id.dialogEditText) - val text = editText?.text.toString() + activity.customAlertDialog().apply { + val customView = DialogEdittextBinding.inflate(layoutInflater) + setTitle("Enter a chapter/episode number tag") + setCustomView(customView.root) + setPosButton("OK") { + val text = customView.dialogEditText.text.toString() filterTag = text.toIntOrNull() lifecycleScope.launch { loadAndDisplayComments() } - - dialog.dismiss() } - .setNeutralButton("Clear") { dialog, _ -> + setNeutralButton("Clear") { filterTag = null lifecycleScope.launch { loadAndDisplayComments() } - dialog.dismiss() } - .setNegativeButton("Cancel") { dialog, _ -> - filterTag = null - dialog.dismiss() - } - val dialog = alertDialog.show() - dialog?.window?.setDimAmount(0.8f) + setNegButton("Cancel") { filterTag = null } + show() + } } var isFetching = false @@ -303,13 +298,12 @@ class CommentsFragment : Fragment() { activity.binding.commentLabel.setOnClickListener { //alert dialog to enter a number, with a cancel and ok button - val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup) - .setTitle("Enter a chapter/episode number tag") - .setView(R.layout.dialog_edittext) - .setPositiveButton("OK") { dialog, _ -> - val editText = - (dialog as AlertDialog).findViewById(R.id.dialogEditText) - val text = editText?.text.toString() + activity.customAlertDialog().apply { + val customView = DialogEdittextBinding.inflate(layoutInflater) + setTitle("Enter a chapter/episode number tag") + setCustomView(customView.root) + setPosButton("OK") { + val text = customView.dialogEditText.text.toString() tag = text.toIntOrNull() if (tag == null) { activity.binding.commentLabel.background = ResourcesCompat.getDrawable( @@ -324,28 +318,25 @@ class CommentsFragment : Fragment() { null ) } - dialog.dismiss() } - .setNeutralButton("Clear") { dialog, _ -> + setNeutralButton("Clear") { tag = null activity.binding.commentLabel.background = ResourcesCompat.getDrawable( resources, R.drawable.ic_label_off_24, null ) - dialog.dismiss() } - .setNegativeButton("Cancel") { dialog, _ -> + setNegButton("Cancel") { tag = null activity.binding.commentLabel.background = ResourcesCompat.getDrawable( resources, R.drawable.ic_label_off_24, null ) - dialog.dismiss() } - val dialog = alertDialog.show() - dialog?.window?.setDimAmount(0.8f) + show() + } } } @@ -363,11 +354,6 @@ class CommentsFragment : Fragment() { } } - @SuppressLint("NotifyDataSetChanged") - override fun onStart() { - super.onStart() - } - @SuppressLint("NotifyDataSetChanged") override fun onResume() { super.onResume() @@ -579,9 +565,9 @@ class CommentsFragment : Fragment() { * Called when the user tries to comment for the first time */ private fun showCommentRulesDialog() { - val alertDialog = AlertDialog.Builder(activity, R.style.MyPopup) - .setTitle("Commenting Rules") - .setMessage( + activity.customAlertDialog().apply{ + setTitle("Commenting Rules") + setMessage( "I WILL BAN YOU WITHOUT HESITATION\n" + "1. No racism\n" + "2. No hate speech\n" + @@ -594,16 +580,13 @@ class CommentsFragment : Fragment() { "10. No illegal content\n" + "11. Anything you know you shouldn't comment\n" ) - .setPositiveButton("I Understand") { dialog, _ -> - dialog.dismiss() + setPosButton("I Understand") { PrefManager.setVal(PrefName.FirstComment, false) processComment() } - .setNegativeButton("Cancel") { dialog, _ -> - dialog.dismiss() - } - val dialog = alertDialog.show() - dialog?.window?.setDimAmount(0.8f) + setNegButton(R.string.cancel) + show() + } } private fun processComment() { diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt index 177d3684..a98f0570 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadAdapter.kt @@ -1,6 +1,5 @@ package ani.dantotsu.media.manga -import android.app.AlertDialog import android.content.Intent import android.view.LayoutInflater import android.view.View @@ -19,8 +18,9 @@ import androidx.recyclerview.widget.RecyclerView import ani.dantotsu.R import ani.dantotsu.currActivity import ani.dantotsu.currContext +import ani.dantotsu.databinding.CustomDialogLayoutBinding import ani.dantotsu.databinding.DialogLayoutBinding -import ani.dantotsu.databinding.ItemAnimeWatchBinding +import ani.dantotsu.databinding.ItemMediaSourceBinding import ani.dantotsu.databinding.ItemChipBinding import ani.dantotsu.isOnline import ani.dantotsu.loadImage @@ -35,11 +35,13 @@ import ani.dantotsu.others.webview.CookieCatcher import ani.dantotsu.parsers.DynamicMangaParser import ani.dantotsu.parsers.MangaReadSources import ani.dantotsu.parsers.MangaSources +import ani.dantotsu.parsers.OfflineMangaParser import ani.dantotsu.px import ani.dantotsu.settings.FAQActivity import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.toast +import ani.dantotsu.util.customAlertDialog import com.google.android.material.chip.Chip import eu.kanade.tachiyomi.data.notification.Notifications.CHANNEL_SUBSCRIPTION_CHECK import eu.kanade.tachiyomi.source.online.HttpSource @@ -55,31 +57,29 @@ class MangaReadAdapter( ) : RecyclerView.Adapter() { var subscribe: MediaDetailsActivity.PopImageButton? = null - private var _binding: ItemAnimeWatchBinding? = null + private var _binding: ItemMediaSourceBinding? = null val hiddenScanlators = mutableListOf() var scanlatorSelectionListener: ScanlatorSelectionListener? = null var options = listOf() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - val bind = ItemAnimeWatchBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val bind = ItemMediaSourceBinding.inflate(LayoutInflater.from(parent.context), parent, false) return ViewHolder(bind) } - private var nestedDialog: AlertDialog? = null - override fun onBindViewHolder(holder: ViewHolder, position: Int) { val binding = holder.binding _binding = binding binding.sourceTitle.setText(R.string.chaps) - //Fuck u launch + // Fuck u launch binding.faqbutton.setOnClickListener { val intent = Intent(fragment.requireContext(), FAQActivity::class.java) startActivity(fragment.requireContext(), intent, null) } - //Wrong Title - binding.animeSourceSearch.setOnClickListener { + // Wrong Title + binding.mediaSourceSearch.setOnClickListener { SourceSearchDialogFragment().show( fragment.requireActivity().supportFragmentManager, null @@ -87,54 +87,54 @@ class MangaReadAdapter( } val offline = !isOnline(binding.root.context) || PrefManager.getVal(PrefName.OfflineMode) - binding.animeSourceNameContainer.isGone = offline - binding.animeSourceSettings.isGone = offline - binding.animeSourceSearch.isGone = offline - binding.animeSourceTitle.isGone = offline - //Source Selection + binding.mediaSourceNameContainer.isGone = offline + binding.mediaSourceSettings.isGone = offline + binding.mediaSourceSearch.isGone = offline + binding.mediaSourceTitle.isGone = offline + // Source Selection var source = media.selected!!.sourceIndex.let { if (it >= mangaReadSources.names.size) 0 else it } setLanguageList(media.selected!!.langIndex, source) if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { - binding.animeSource.setText(mangaReadSources.names[source]) + binding.mediaSource.setText(mangaReadSources.names[source]) mangaReadSources[source].apply { - binding.animeSourceTitle.text = showUserText - showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + binding.mediaSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } } } } media.selected?.scanlators?.let { hiddenScanlators.addAll(it) } - binding.animeSource.setAdapter( + binding.mediaSource.setAdapter( ArrayAdapter( fragment.requireContext(), R.layout.item_dropdown, mangaReadSources.names ) ) - binding.animeSourceTitle.isSelected = true - binding.animeSource.setOnItemClickListener { _, _, i, _ -> + binding.mediaSourceTitle.isSelected = true + binding.mediaSource.setOnItemClickListener { _, _, i, _ -> fragment.onSourceChange(i).apply { - binding.animeSourceTitle.text = showUserText - showUserTextListener = { MainScope().launch { binding.animeSourceTitle.text = it } } + binding.mediaSourceTitle.text = showUserText + showUserTextListener = { MainScope().launch { binding.mediaSourceTitle.text = it } } source = i setLanguageList(0, i) } subscribeButton(false) - //invalidate if it's the last source + // Invalidate if it's the last source val invalidate = i == mangaReadSources.names.size - 1 fragment.loadChapters(i, invalidate) } - binding.animeSourceLanguage.setOnItemClickListener { _, _, i, _ -> + binding.mediaSourceLanguage.setOnItemClickListener { _, _, i, _ -> // Check if 'extension' and 'selected' properties exist and are accessible (mangaReadSources[source] as? DynamicMangaParser)?.let { ext -> ext.sourceLanguage = i fragment.onLangChange(i, ext.saveName) fragment.onSourceChange(media.selected!!.sourceIndex).apply { - binding.animeSourceTitle.text = showUserText + binding.mediaSourceTitle.text = showUserText showUserTextListener = - { MainScope().launch { binding.animeSourceTitle.text = it } } + { MainScope().launch { binding.mediaSourceTitle.text = it } } setLanguageList(i, source) } subscribeButton(false) @@ -143,17 +143,17 @@ class MangaReadAdapter( } } - //settings - binding.animeSourceSettings.setOnClickListener { + // Settings + binding.mediaSourceSettings.setOnClickListener { (mangaReadSources[source] as? DynamicMangaParser)?.let { ext -> fragment.openSettings(ext.extension) } } - //Grids + // Grids subscribe = MediaDetailsActivity.PopImageButton( fragment.lifecycleScope, - binding.animeSourceSubscribe, + binding.mediaSourceSubscribe, R.drawable.ic_round_notifications_active_24, R.drawable.ic_round_notifications_none_24, R.color.bg_opp, @@ -161,206 +161,191 @@ class MangaReadAdapter( fragment.subscribed, true ) { - fragment.onNotificationPressed(it, binding.animeSource.text.toString()) + fragment.onNotificationPressed(it, binding.mediaSource.text.toString()) } subscribeButton(false) - binding.animeSourceSubscribe.setOnLongClickListener { + binding.mediaSourceSubscribe.setOnLongClickListener { openSettings(fragment.requireContext(), CHANNEL_SUBSCRIPTION_CHECK) } - binding.animeNestedButton.setOnClickListener { - - val dialogView = - LayoutInflater.from(fragment.requireContext()).inflate(R.layout.dialog_layout, null) - val dialogBinding = DialogLayoutBinding.bind(dialogView) + binding.mediaNestedButton.setOnClickListener { + val dialogBinding = DialogLayoutBinding.inflate(fragment.layoutInflater) var refresh = false var run = false var reversed = media.selected!!.recyclerReversed var style = media.selected!!.recyclerStyle ?: PrefManager.getVal(PrefName.MangaDefaultView) - dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f - dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" - dialogBinding.animeSourceTop.setOnClickListener { - reversed = !reversed - dialogBinding.animeSourceTop.rotation = if (reversed) -90f else 90f - dialogBinding.sortText.text = if (reversed) "Down to Up" else "Up to Down" - run = true - } + dialogBinding.apply { + mediaSourceTop.rotation = if (reversed) -90f else 90f + sortText.text = if (reversed) "Down to Up" else "Up to Down" + mediaSourceTop.setOnClickListener { + reversed = !reversed + mediaSourceTop.rotation = if (reversed) -90f else 90f + sortText.text = if (reversed) "Down to Up" else "Up to Down" + run = true + } - //Grids - dialogBinding.animeSourceGrid.visibility = View.GONE - var selected = when (style) { - 0 -> dialogBinding.animeSourceList - 1 -> dialogBinding.animeSourceCompact - else -> dialogBinding.animeSourceList - } - when (style) { - 0 -> dialogBinding.layoutText.setText(R.string.list) - 1 -> dialogBinding.layoutText.setText(R.string.compact) - else -> dialogBinding.animeSourceList - } - selected.alpha = 1f - fun selected(it: ImageButton) { - selected.alpha = 0.33f - selected = it + // Grids + mediaSourceGrid.visibility = View.GONE + var selected = when (style) { + 0 -> mediaSourceList + 1 -> mediaSourceCompact + else -> mediaSourceList + } + when (style) { + 0 -> layoutText.setText(R.string.list) + 1 -> layoutText.setText(R.string.compact) + else -> mediaSourceList + } selected.alpha = 1f - } - dialogBinding.animeSourceList.setOnClickListener { - selected(it as ImageButton) - style = 0 - dialogBinding.layoutText.setText(R.string.list) - run = true - } - dialogBinding.animeSourceCompact.setOnClickListener { - selected(it as ImageButton) - style = 1 - dialogBinding.layoutText.setText(R.string.compact) - run = true - } - dialogBinding.animeWebviewContainer.setOnClickListener { - if (!WebViewUtil.supportsWebView(fragment.requireContext())) { - toast(R.string.webview_not_installed) + fun selected(it: ImageButton) { + selected.alpha = 0.33f + selected = it + selected.alpha = 1f } - //start CookieCatcher activity - if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { - val sourceAHH = mangaReadSources[source] as? DynamicMangaParser - val sourceHttp = sourceAHH?.extension?.sources?.firstOrNull() as? HttpSource - val url = sourceHttp?.baseUrl - url?.let { - refresh = true - val intent = Intent(fragment.requireContext(), CookieCatcher::class.java) - .putExtra("url", url) - startActivity(fragment.requireContext(), intent, null) + mediaSourceList.setOnClickListener { + selected(it as ImageButton) + style = 0 + layoutText.setText(R.string.list) + run = true + } + mediaSourceCompact.setOnClickListener { + selected(it as ImageButton) + style = 1 + layoutText.setText(R.string.compact) + run = true + } + mediaWebviewContainer.setOnClickListener { + if (!WebViewUtil.supportsWebView(fragment.requireContext())) { + toast(R.string.webview_not_installed) } - } - } - - //Multi download - dialogBinding.downloadNo.text = "0" - dialogBinding.animeDownloadTop.setOnClickListener { - //Alert dialog asking for the number of chapters to download - val alertDialog = AlertDialog.Builder(currContext(), R.style.MyPopup) - alertDialog.setTitle("Multi Chapter Downloader") - alertDialog.setMessage("Enter the number of chapters to download") - val input = NumberPicker(currContext()) - input.minValue = 1 - input.maxValue = 20 - input.value = 1 - alertDialog.setView(input) - alertDialog.setPositiveButton("OK") { _, _ -> - dialogBinding.downloadNo.text = "${input.value}" - } - alertDialog.setNegativeButton("Cancel") { dialog, _ -> dialog.cancel() } - val dialog = alertDialog.show() - dialog.window?.setDimAmount(0.8f) - } - - //Scanlator - dialogBinding.animeScanlatorContainer.isVisible = options.count() > 1 - dialogBinding.scanlatorNo.text = "${options.count()}" - dialogBinding.animeScanlatorTop.setOnClickListener { - val dialogView2 = - LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null) - val checkboxContainer = - dialogView2.findViewById(R.id.checkboxContainer) - val tickAllButton = dialogView2.findViewById(R.id.toggleButton) - - // Function to get the right image resource for the toggle button - fun getToggleImageResource(container: ViewGroup): Int { - var allChecked = true - var allUnchecked = true - - for (i in 0 until container.childCount) { - val checkBox = container.getChildAt(i) as CheckBox - if (!checkBox.isChecked) { - allChecked = false - } else { - allUnchecked = false + // Start CookieCatcher activity + if (mangaReadSources.names.isNotEmpty() && source in 0 until mangaReadSources.names.size) { + val sourceAHH = mangaReadSources[source] as? DynamicMangaParser + val sourceHttp = sourceAHH?.extension?.sources?.firstOrNull() as? HttpSource + val url = sourceHttp?.baseUrl + url?.let { + refresh = true + val intent = + Intent(fragment.requireContext(), CookieCatcher::class.java) + .putExtra("url", url) + startActivity(fragment.requireContext(), intent, null) } } - return when { - allChecked -> R.drawable.untick_all_boxes - allUnchecked -> R.drawable.tick_all_boxes - else -> R.drawable.invert_all_boxes - } } - // Dynamically add checkboxes - options.forEach { option -> - val checkBox = CheckBox(currContext()).apply { - text = option - setOnCheckedChangeListener { _, _ -> - // Update image resource when you change a checkbox - tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) + // Multi download + downloadNo.text = "0" + mediaDownloadTop.setOnClickListener { + // Alert dialog asking for the number of chapters to download + fragment.requireContext().customAlertDialog().apply { + setTitle("Multi Chapter Downloader") + setMessage("Enter the number of chapters to download") + val input = NumberPicker(currContext()) + input.minValue = 1 + input.maxValue = 20 + input.value = 1 + setCustomView(input) + setPosButton(R.string.ok) { + downloadNo.text = "${input.value}" } + setNegButton(R.string.cancel) + show() } - - // Set checked if its already selected - if (media.selected!!.scanlators != null) { - checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true - scanlatorSelectionListener?.onScanlatorsSelected() - } else { - checkBox.isChecked = true - } - checkboxContainer.addView(checkBox) } - // Create AlertDialog - val dialog = AlertDialog.Builder(currContext(), R.style.MyPopup) - .setView(dialogView2) - .setPositiveButton("OK") { _, _ -> - hiddenScanlators.clear() - for (i in 0 until checkboxContainer.childCount) { - val checkBox = checkboxContainer.getChildAt(i) as CheckBox + // Scanlator + mangaScanlatorContainer.isVisible = options.count() > 1 + scanlatorNo.text = "${options.count()}" + mangaScanlatorTop.setOnClickListener { + CustomDialogLayoutBinding.inflate(fragment.layoutInflater) + val dialogView = CustomDialogLayoutBinding.inflate(fragment.layoutInflater) + val checkboxContainer = dialogView.checkboxContainer + val tickAllButton = dialogView.toggleButton + + fun getToggleImageResource(container: ViewGroup): Int { + var allChecked = true + var allUnchecked = true + + for (i in 0 until container.childCount) { + val checkBox = container.getChildAt(i) as CheckBox if (!checkBox.isChecked) { - hiddenScanlators.add(checkBox.text.toString()) + allChecked = false + } else { + allUnchecked = false } } - fragment.onScanlatorChange(hiddenScanlators) - scanlatorSelectionListener?.onScanlatorsSelected() - } - .setNegativeButton("Cancel", null) - .show() - dialog.window?.setDimAmount(0.8f) - - // Standard image resource - tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) - - // Listens to ticked checkboxes and changes image resource accordingly - tickAllButton.setOnClickListener { - // Toggle checkboxes - for (i in 0 until checkboxContainer.childCount) { - val checkBox = checkboxContainer.getChildAt(i) as CheckBox - checkBox.isChecked = !checkBox.isChecked + return when { + allChecked -> R.drawable.untick_all_boxes + allUnchecked -> R.drawable.tick_all_boxes + else -> R.drawable.invert_all_boxes + } } - // Update image resource + options.forEach { option -> + val checkBox = CheckBox(currContext()).apply { + text = option + setOnCheckedChangeListener { _, _ -> + tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) + } + } + + if (media.selected!!.scanlators != null) { + checkBox.isChecked = media.selected!!.scanlators?.contains(option) != true + scanlatorSelectionListener?.onScanlatorsSelected() + } else { + checkBox.isChecked = true + } + checkboxContainer.addView(checkBox) + } + + fragment.requireContext().customAlertDialog().apply { + setCustomView(dialogView.root) + setPosButton("OK") { + hiddenScanlators.clear() + for (i in 0 until checkboxContainer.childCount) { + val checkBox = checkboxContainer.getChildAt(i) as CheckBox + if (!checkBox.isChecked) { + hiddenScanlators.add(checkBox.text.toString()) + } + } + fragment.onScanlatorChange(hiddenScanlators) + scanlatorSelectionListener?.onScanlatorsSelected() + } + setNegButton("Cancel") + }.show() + tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) + + tickAllButton.setOnClickListener { + for (i in 0 until checkboxContainer.childCount) { + val checkBox = checkboxContainer.getChildAt(i) as CheckBox + checkBox.isChecked = !checkBox.isChecked + } + tickAllButton.setImageResource(getToggleImageResource(checkboxContainer)) + } + } + + fragment.requireContext().customAlertDialog().apply { + setTitle("Options") + setCustomView(root) + setPosButton("OK") { + if (run) fragment.onIconPressed(style, reversed) + if (downloadNo.text != "0") { + fragment.multiDownload(downloadNo.text.toString().toInt()) + } + if (refresh) fragment.loadChapters(source, true) + } + setNegButton("Cancel") { + if (refresh) fragment.loadChapters(source, true) + } + show() } } - - nestedDialog = AlertDialog.Builder(fragment.requireContext(), R.style.MyPopup) - .setTitle("Options") - .setView(dialogView) - .setPositiveButton("OK") { _, _ -> - if (run) fragment.onIconPressed(style, reversed) - if (dialogBinding.downloadNo.text != "0") { - fragment.multiDownload(dialogBinding.downloadNo.text.toString().toInt()) - } - if (refresh) fragment.loadChapters(source, true) - } - .setNegativeButton("Cancel") { _, _ -> - if (refresh) fragment.loadChapters(source, true) - } - .setOnCancelListener { - if (refresh) fragment.loadChapters(source, true) - } - .create() - nestedDialog?.show() } - //Chapter Handling + // Chapter Handling handleChapters() } @@ -368,7 +353,7 @@ class MangaReadAdapter( subscribe?.enabled(enabled) } - //Chips + // Chips fun updateChips(limit: Int, names: Array, arr: Array, selected: Int = 0) { val binding = _binding if (binding != null) { @@ -379,13 +364,13 @@ class MangaReadAdapter( val chip = ItemChipBinding.inflate( LayoutInflater.from(fragment.context), - binding.animeSourceChipGroup, + binding.mediaSourceChipGroup, false ).root chip.isCheckable = true fun selected() { chip.isChecked = true - binding.animeWatchChipScroll.smoothScrollTo( + binding.mediaWatchChipScroll.smoothScrollTo( (chip.left - screenWidth / 2) + (chip.width / 2), 0 ) @@ -403,7 +388,7 @@ class MangaReadAdapter( } else { names[last - 1] } - //chip.text = "${names[limit * (position)]} - ${names[last - 1]}" + // chip.text = "${names[limit * (position)]} - ${names[last - 1]}" val chipText = "$startChapterString - $endChapterString" chip.text = chipText chip.setTextColor( @@ -417,14 +402,14 @@ class MangaReadAdapter( selected() fragment.onChipClicked(position, limit * (position), last - 1) } - binding.animeSourceChipGroup.addView(chip) + binding.mediaSourceChipGroup.addView(chip) if (selected == position) { selected() select = chip } } if (select != null) - binding.animeWatchChipScroll.apply { + binding.mediaWatchChipScroll.apply { post { scrollTo( (select.left - screenWidth / 2) + (select.width / 2), @@ -436,7 +421,7 @@ class MangaReadAdapter( } fun clearChips() { - _binding?.animeSourceChipGroup?.removeAllViews() + _binding?.mediaSourceChipGroup?.removeAllViews() } fun handleChapters() { @@ -462,70 +447,86 @@ class MangaReadAdapter( } if (formattedChapters.contains(continueEp)) { continueEp = chapters[formattedChapters.indexOf(continueEp)] - binding.animeSourceContinue.visibility = View.VISIBLE + binding.sourceContinue.visibility = View.VISIBLE handleProgress( - binding.itemEpisodeProgressCont, - binding.itemEpisodeProgress, - binding.itemEpisodeProgressEmpty, + binding.itemMediaProgressCont, + binding.itemMediaProgress, + binding.itemMediaProgressEmpty, media.id, continueEp ) - if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight > 0.8f) { + if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight > 0.8f) { val e = chapters.indexOf(continueEp) if (e != -1 && e + 1 < chapters.size) { continueEp = chapters[e + 1] } } val ep = media.manga.chapters!![continueEp]!! - binding.itemEpisodeImage.loadImage(media.banner ?: media.cover) - binding.animeSourceContinueText.text = + binding.itemMediaImage.loadImage(media.banner ?: media.cover) + binding.mediaSourceContinueText.text = currActivity()!!.getString( R.string.continue_chapter, ep.number, if (!ep.title.isNullOrEmpty()) ep.title else "" ) - binding.animeSourceContinue.setOnClickListener { + binding.sourceContinue.setOnClickListener { fragment.onMangaChapterClick(continueEp) } if (fragment.continueEp) { - if ((binding.itemEpisodeProgress.layoutParams as LinearLayout.LayoutParams).weight < 0.8f) { - binding.animeSourceContinue.performClick() + if ((binding.itemMediaProgress.layoutParams as LinearLayout.LayoutParams).weight < 0.8f) { + binding.sourceContinue.performClick() fragment.continueEp = false } } } else { - binding.animeSourceContinue.visibility = View.GONE + binding.sourceContinue.visibility = View.GONE } - binding.animeSourceProgressBar.visibility = View.GONE - val sourceFound = media.manga.chapters!!.isNotEmpty() - binding.animeSourceNotFound.isGone = sourceFound + + binding.sourceProgressBar.visibility = View.GONE + + val sourceFound = filteredChapters.isNotEmpty() + val isDownloadedSource = mangaReadSources[media.selected!!.sourceIndex] is OfflineMangaParser + + if (isDownloadedSource) { + binding.sourceNotFound.text = if (sourceFound) { + currActivity()!!.getString(R.string.source_not_found) + } else { + currActivity()!!.getString(R.string.download_not_found) + } + } else { + binding.sourceNotFound.text = currActivity()!!.getString(R.string.source_not_found) + } + + binding.sourceNotFound.isGone = sourceFound binding.faqbutton.isGone = sourceFound + + if (!sourceFound && PrefManager.getVal(PrefName.SearchSources)) { - if (binding.animeSource.adapter.count > media.selected!!.sourceIndex + 1) { + if (binding.mediaSource.adapter.count > media.selected!!.sourceIndex + 1) { val nextIndex = media.selected!!.sourceIndex + 1 - binding.animeSource.setText( - binding.animeSource.adapter + binding.mediaSource.setText( + binding.mediaSource.adapter .getItem(nextIndex).toString(), false ) fragment.onSourceChange(nextIndex).apply { - binding.animeSourceTitle.text = showUserText + binding.mediaSourceTitle.text = showUserText showUserTextListener = - { MainScope().launch { binding.animeSourceTitle.text = it } } + { MainScope().launch { binding.mediaSourceTitle.text = it } } setLanguageList(0, nextIndex) } subscribeButton(false) - // invalidate if it's the last source + // Invalidate if it's the last source val invalidate = nextIndex == mangaReadSources.names.size - 1 fragment.loadChapters(nextIndex, invalidate) } } } else { - binding.animeSourceContinue.visibility = View.GONE - binding.animeSourceNotFound.visibility = View.GONE + binding.sourceContinue.visibility = View.GONE + binding.sourceNotFound.visibility = View.GONE binding.faqbutton.visibility = View.GONE clearChips() - binding.animeSourceProgressBar.visibility = View.VISIBLE + binding.sourceProgressBar.visibility = View.VISIBLE } } } @@ -539,9 +540,9 @@ class MangaReadAdapter( ext.sourceLanguage = lang } try { - binding?.animeSourceLanguage?.setText(parser.extension.sources[lang].lang) + binding?.mediaSourceLanguage?.setText(parser.extension.sources[lang].lang) } catch (e: IndexOutOfBoundsException) { - binding?.animeSourceLanguage?.setText( + binding?.mediaSourceLanguage?.setText( parser.extension.sources.firstOrNull()?.lang ?: "Unknown" ) } @@ -551,9 +552,9 @@ class MangaReadAdapter( parser.extension.sources.map { LanguageMapper.getLanguageName(it.lang) } ) val items = adapter.count - binding?.animeSourceLanguageContainer?.isVisible = items > 1 + binding?.mediaSourceLanguageContainer?.isVisible = items > 1 - binding?.animeSourceLanguage?.setAdapter(adapter) + binding?.mediaSourceLanguage?.setAdapter(adapter) } } @@ -561,7 +562,7 @@ class MangaReadAdapter( override fun getItemCount(): Int = 1 - inner class ViewHolder(val binding: ItemAnimeWatchBinding) : + inner class ViewHolder(val binding: ItemMediaSourceBinding) : RecyclerView.ViewHolder(binding.root) } diff --git a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt index bce5f312..be836ad5 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/MangaReadFragment.kt @@ -2,7 +2,6 @@ package ani.dantotsu.media.manga import android.Manifest import android.annotation.SuppressLint -import android.app.AlertDialog import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -31,7 +30,7 @@ import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.R -import ani.dantotsu.databinding.FragmentAnimeWatchBinding +import ani.dantotsu.databinding.FragmentMediaSourceBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.DownloadsManager.Companion.compareName @@ -60,6 +59,7 @@ import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.StoragePermissions.Companion.accessAlertDialog import ani.dantotsu.util.StoragePermissions.Companion.hasDirAccess +import ani.dantotsu.util.customAlertDialog import com.google.android.material.appbar.AppBarLayout import eu.kanade.tachiyomi.extension.manga.model.MangaExtension import eu.kanade.tachiyomi.source.ConfigurableSource @@ -74,7 +74,7 @@ import kotlin.math.max import kotlin.math.roundToInt open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { - private var _binding: FragmentAnimeWatchBinding? = null + private var _binding: FragmentMediaSourceBinding? = null private val binding get() = _binding!! private val model: MediaDetailsViewModel by activityViewModels() @@ -101,7 +101,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = FragmentAnimeWatchBinding.inflate(inflater, container, false) + _binding = FragmentMediaSourceBinding.inflate(inflater, container, false) return _binding?.root } @@ -121,7 +121,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { ContextCompat.RECEIVER_EXPORTED ) - binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight) + binding.mediaSourceRecycler.updatePadding(bottom = binding.mediaSourceRecycler.paddingBottom + navBarHeight) screenWidth = resources.displayMetrics.widthPixels.dp var maxGridSize = (screenWidth / 100f).roundToInt() @@ -144,13 +144,13 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { } } - binding.animeSourceRecycler.layoutManager = gridLayoutManager + binding.mediaSourceRecycler.layoutManager = gridLayoutManager binding.ScrollTop.setOnClickListener { - binding.animeSourceRecycler.scrollToPosition(10) - binding.animeSourceRecycler.smoothScrollToPosition(0) + binding.mediaSourceRecycler.scrollToPosition(10) + binding.mediaSourceRecycler.smoothScrollToPosition(0) } - binding.animeSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { + binding.mediaSourceRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) @@ -164,7 +164,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { } }) model.scrolledToTop.observe(viewLifecycleOwner) { - if (it) binding.animeSourceRecycler.scrollToPosition(0) + if (it) binding.mediaSourceRecycler.scrollToPosition(0) } continueEp = model.continueMedia ?: false @@ -199,7 +199,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { } } - binding.animeSourceRecycler.adapter = + binding.mediaSourceRecycler.adapter = ConcatAdapter(headerAdapter, chapterAdapter) lifecycleScope.launch(Dispatchers.IO) { @@ -214,8 +214,8 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { reload() } } else { - binding.animeNotSupported.visibility = View.VISIBLE - binding.animeNotSupported.text = + binding.mediaNotSupported.visibility = View.VISIBLE + binding.mediaNotSupported.text = getString(R.string.not_supported, media.format ?: "") } } @@ -231,10 +231,10 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { } fun multiDownload(n: Int) { - //get last viewed chapter + // Get last viewed chapter val selected = media.userProgress val chapters = media.manga?.chapters?.values?.toList() - //filter by selected language + // Filter by selected language val progressChapterIndex = (chapters?.indexOfFirst { MediaNameAdapter.findChapterNumber(it.number)?.toInt() == selected } ?: 0) + 1 @@ -244,7 +244,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { // Calculate the end index val endIndex = minOf(progressChapterIndex + n, chapters.size) - //make sure there are enough chapters + // Make sure there are enough chapters val chaptersToDownload = chapters.subList(progressChapterIndex, endIndex) @@ -386,32 +386,30 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { if (allSettings.size > 1) { val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) }.toTypedArray() - val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) - .setTitle("Select a Source") - .setSingleChoiceItems(names, -1) { dialog, which -> + requireContext().customAlertDialog().apply { + setTitle("Select a Source") + singleChoiceItems(names) { which -> selectedSetting = allSettings[which] itemSelected = true - dialog.dismiss() - // Move the fragment transaction here - val fragment = - MangaSourcePreferencesFragment().getInstance(selectedSetting.id) { - changeUIVisibility(true) - loadChapters(media.selected!!.sourceIndex, true) - } + val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id) { + changeUIVisibility(true) + loadChapters(media.selected!!.sourceIndex, true) + } parentFragmentManager.beginTransaction() .setCustomAnimations(R.anim.slide_up, R.anim.slide_down) .replace(R.id.fragmentExtensionsContainer, fragment) .addToBackStack(null) .commit() } - .setOnDismissListener { + onDismiss{ if (!itemSelected) { changeUIVisibility(true) } } - .show() - dialog.window?.setDimAmount(0.8f) + show() + + } } else { // If there's only one setting, proceed with the fragment transaction val fragment = MangaSourcePreferencesFragment().getInstance(selectedSetting.id) { @@ -584,7 +582,7 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { private fun reload() { val selected = model.loadSelected(media) - //Find latest chapter for subscription + // Find latest chapter for subscription selected.latest = media.manga?.chapters?.values?.maxOfOrNull { it.number.toFloatOrNull() ?: 0f } ?: 0f selected.latest = @@ -618,14 +616,14 @@ open class MangaReadFragment : Fragment(), ScanlatorSelectionListener { override fun onResume() { super.onResume() binding.mediaInfoProgressBar.visibility = progress - binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state) + binding.mediaSourceRecycler.layoutManager?.onRestoreInstanceState(state) requireActivity().setNavigationTheme() } override fun onPause() { super.onPause() - state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState() + state = binding.mediaSourceRecycler.layoutManager?.onSaveInstanceState() } companion object { diff --git a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt index 86085f58..bb28d7aa 100644 --- a/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/manga/mangareader/MangaReaderActivity.kt @@ -83,6 +83,7 @@ import ani.dantotsu.showSystemBarsRetractView import ani.dantotsu.snackString import ani.dantotsu.themes.ThemeManager import ani.dantotsu.tryWith +import ani.dantotsu.util.customAlertDialog import com.alexvasilkov.gestures.views.GestureFrameLayout import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView @@ -1013,28 +1014,27 @@ class MangaReaderActivity : AppCompatActivity() { PrefManager.setCustomVal("${media.id}_progressDialog", !isChecked) showProgressDialog = !isChecked } - AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.title_update_progress)) - .setView(dialogView) - .setCancelable(false) - .setPositiveButton(getString(R.string.yes)) { dialog, _ -> + customAlertDialog().apply { + setTitle(R.string.title_update_progress) + setCustomView(dialogView) + setCancelable(false) + setPosButton(R.string.yes) { PrefManager.setCustomVal("${media.id}_save_progress", true) updateProgress( media, MediaNameAdapter.findChapterNumber(media.manga!!.selectedChapter!!) .toString() ) - dialog.dismiss() runnable.run() } - .setNegativeButton(getString(R.string.no)) { dialog, _ -> + setNegButton(R.string.no) { PrefManager.setCustomVal("${media.id}_save_progress", false) - dialog.dismiss() runnable.run() } - .setOnCancelListener { hideSystemBars() } - .create() - .show() + setOnCancelListener { hideSystemBars() } + show() + + } } else { if (!incognito && PrefManager.getCustomVal( "${media.id}_save_progress", diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelReadAdapter.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelReadAdapter.kt index 7f9d34f4..cfaf298f 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelReadAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelReadAdapter.kt @@ -50,16 +50,16 @@ class NovelReadAdapter( val source = media.selected!!.sourceIndex.let { if (it >= novelReadSources.names.size) 0 else it } if (novelReadSources.names.isNotEmpty() && source in 0 until novelReadSources.names.size) { - binding.animeSource.setText(novelReadSources.names[source], false) + binding.mediaSource.setText(novelReadSources.names[source], false) } - binding.animeSource.setAdapter( + binding.mediaSource.setAdapter( ArrayAdapter( fragment.requireContext(), R.layout.item_dropdown, novelReadSources.names ) ) - binding.animeSource.setOnItemClickListener { _, _, i, _ -> + binding.mediaSource.setOnItemClickListener { _, _, i, _ -> fragment.onSourceChange(i) search() } diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt index c95328f6..5cd1f8ad 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelReadFragment.kt @@ -20,7 +20,7 @@ import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import ani.dantotsu.R import ani.dantotsu.currContext -import ani.dantotsu.databinding.FragmentAnimeWatchBinding +import ani.dantotsu.databinding.FragmentMediaSourceBinding import ani.dantotsu.download.DownloadedType import ani.dantotsu.download.DownloadsManager import ani.dantotsu.download.novel.NovelDownloaderService @@ -47,7 +47,7 @@ class NovelReadFragment : Fragment(), DownloadTriggerCallback, DownloadedCheckCallback { - private var _binding: FragmentAnimeWatchBinding? = null + private var _binding: FragmentMediaSourceBinding? = null private val binding get() = _binding!! private val model: MediaDetailsViewModel by activityViewModels() @@ -214,11 +214,11 @@ class NovelReadFragment : Fragment(), ContextCompat.RECEIVER_EXPORTED ) - binding.animeSourceRecycler.updatePadding(bottom = binding.animeSourceRecycler.paddingBottom + navBarHeight) + binding.mediaSourceRecycler.updatePadding(bottom = binding.mediaSourceRecycler.paddingBottom + navBarHeight) - binding.animeSourceRecycler.layoutManager = LinearLayoutManager(requireContext()) + binding.mediaSourceRecycler.layoutManager = LinearLayoutManager(requireContext()) model.scrolledToTop.observe(viewLifecycleOwner) { - if (it) binding.animeSourceRecycler.scrollToPosition(0) + if (it) binding.mediaSourceRecycler.scrollToPosition(0) } continueEp = model.continueMedia ?: false @@ -237,7 +237,7 @@ class NovelReadFragment : Fragment(), this, this ) // probably a better way to do this but it works - binding.animeSourceRecycler.adapter = + binding.mediaSourceRecycler.adapter = ConcatAdapter(headerAdapter, novelResponseAdapter) loaded = true Handler(Looper.getMainLooper()).postDelayed({ @@ -290,7 +290,7 @@ class NovelReadFragment : Fragment(), container: ViewGroup?, savedInstanceState: Bundle? ): View? { - _binding = FragmentAnimeWatchBinding.inflate(inflater, container, false) + _binding = FragmentMediaSourceBinding.inflate(inflater, container, false) return _binding?.root } @@ -304,12 +304,12 @@ class NovelReadFragment : Fragment(), override fun onResume() { super.onResume() binding.mediaInfoProgressBar.visibility = progress - binding.animeSourceRecycler.layoutManager?.onRestoreInstanceState(state) + binding.mediaSourceRecycler.layoutManager?.onRestoreInstanceState(state) } override fun onPause() { super.onPause() - state = binding.animeSourceRecycler.layoutManager?.onSaveInstanceState() + state = binding.mediaSourceRecycler.layoutManager?.onSaveInstanceState() } companion object { diff --git a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt index 92ddc739..7bc304cd 100644 --- a/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/novel/NovelResponseAdapter.kt @@ -13,6 +13,7 @@ import ani.dantotsu.parsers.ShowResponse import ani.dantotsu.setAnimation import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.bumptech.glide.Glide import com.bumptech.glide.load.model.GlideUrl @@ -38,7 +39,7 @@ class NovelResponseAdapter( val binding = holder.binding val novel = list[position] setAnimation(fragment.requireContext(), holder.binding.root) - binding.itemEpisodeImage.loadImage(novel.coverUrl, 400, 0) + binding.itemMediaImage.loadImage(novel.coverUrl, 400, 0) val color =fragment.requireContext().getThemeColor(com.google.android.material.R.attr.colorOnBackground) binding.itemEpisodeTitle.text = novel.name @@ -93,27 +94,22 @@ class NovelResponseAdapter( } binding.root.setOnLongClickListener { - val builder = androidx.appcompat.app.AlertDialog.Builder( - fragment.requireContext(), - R.style.MyPopup - ) - builder.setTitle("Delete ${novel.name}?") - builder.setMessage("Are you sure you want to delete ${novel.name}?") - builder.setPositiveButton("Yes") { _, _ -> - downloadedCheckCallback.deleteDownload(novel) - deleteDownload(novel.link) - snackString("Deleted ${novel.name}") - if (binding.itemEpisodeFiller.text.toString() - .contains("Download", ignoreCase = true) - ) { - binding.itemEpisodeFiller.text = "" + it.context.customAlertDialog().apply { + setTitle("Delete ${novel.name}?") + setMessage("Are you sure you want to delete ${novel.name}?") + setPosButton(R.string.yes) { + downloadedCheckCallback.deleteDownload(novel) + deleteDownload(novel.link) + snackString("Deleted ${novel.name}") + if (binding.itemEpisodeFiller.text.toString() + .contains("Download", ignoreCase = true) + ) { + binding.itemEpisodeFiller.text = "" + } } + setNegButton(R.string.no) + show() } - builder.setNegativeButton("No") { _, _ -> - // Do nothing - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) true } } diff --git a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt index 994a0c78..08109594 100644 --- a/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt +++ b/app/src/main/java/ani/dantotsu/media/user/ListActivity.kt @@ -47,6 +47,7 @@ class ListActivity : AppCompatActivity() { window.statusBarColor = primaryColor window.navigationBarColor = primaryColor + binding.listed.visibility = View.GONE binding.listTabLayout.setBackgroundColor(primaryColor) binding.listAppBar.setBackgroundColor(primaryColor) binding.listTitle.setTextColor(primaryTextColor) diff --git a/app/src/main/java/ani/dantotsu/others/AlignTagHandler.kt b/app/src/main/java/ani/dantotsu/others/AlignTagHandler.kt new file mode 100644 index 00000000..1708ed05 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/others/AlignTagHandler.kt @@ -0,0 +1,32 @@ +package ani.dantotsu.others + +import android.text.Layout +import android.text.style.AlignmentSpan +import io.noties.markwon.MarkwonConfiguration +import io.noties.markwon.RenderProps +import io.noties.markwon.html.HtmlTag +import io.noties.markwon.html.tag.SimpleTagHandler + + +class AlignTagHandler : SimpleTagHandler() { + + override fun getSpans( + configuration: MarkwonConfiguration, + renderProps: RenderProps, + tag: HtmlTag + ): Any { + val alignment: Layout.Alignment = if (tag.attributes().containsKey("center")) { + Layout.Alignment.ALIGN_CENTER + } else if (tag.attributes().containsKey("end")) { + Layout.Alignment.ALIGN_OPPOSITE + } else { + Layout.Alignment.ALIGN_NORMAL + } + + return AlignmentSpan.Standard(alignment) + } + + override fun supportedTags(): Collection { + return setOf("align") + } +} diff --git a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt index 119f4d2f..f343d70b 100644 --- a/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt +++ b/app/src/main/java/ani/dantotsu/parsers/AnimeSources.kt @@ -39,7 +39,7 @@ object AnimeSources : WatchSources() { } fun performReorderAnimeSources() { - //remove the downloaded source from the list to avoid duplicates + // Remove the downloaded source from the list to avoid duplicates list = list.filter { it.name != "Downloaded" } list = sortPinnedAnimeSources(list, pinnedAnimeSources) + Lazier( { OfflineAnimeParser() }, diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt index fa7826b7..8b1500bd 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityFragment.kt @@ -43,30 +43,16 @@ class ActivityFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - type = arguments?.getSerializableCompat("type") as ActivityType - userId = arguments?.getInt("userId") - activityId = arguments?.getInt("activityId") - binding.titleBar.visibility = if (type == ActivityType.OTHER_USER) View.VISIBLE else View.GONE - binding.titleText.text = if (userId == Anilist.userid) getString(R.string.create_new_activity) else getString(R.string.write_a_message) - binding.titleImage.setOnClickListener { - if(userId == Anilist.userid) { - ContextCompat.startActivity( - requireContext(), - Intent(context, ActivityMarkdownCreator::class.java) - .putExtra("type", "activity"), - null - ) - } else{ - ContextCompat.startActivity( - requireContext(), - Intent(context, ActivityMarkdownCreator::class.java) - .putExtra("type", "message") - .putExtra("userId", userId), - - null - ) - } + arguments?.let { + type = it.getSerializableCompat("type") as ActivityType + userId = it.getInt("userId") + activityId = it.getInt("activityId") } + binding.titleBar.visibility = + if (type == ActivityType.OTHER_USER) View.VISIBLE else View.GONE + binding.titleText.text = + if (userId == Anilist.userid) getString(R.string.create_new_activity) else getString(R.string.write_a_message) + binding.titleImage.setOnClickListener { handleTitleImageClick() } binding.listRecyclerView.adapter = adapter binding.listRecyclerView.layoutManager = LinearLayoutManager(context) binding.listProgressBar.isVisible = true @@ -74,7 +60,7 @@ class ActivityFragment : Fragment() { binding.feedRefresh.updateLayoutParams { bottomMargin = navBarHeight } - binding.emptyTextView.text = getString(R.string.no_activities) + binding.emptyTextView.text = getString(R.string.nothing_here) lifecycleScope.launch { getList() if (adapter.itemCount == 0) { @@ -105,6 +91,13 @@ class ActivityFragment : Fragment() { }) } + private fun handleTitleImageClick() { + val intent = Intent(context, ActivityMarkdownCreator::class.java).apply { + putExtra("type", if (userId == Anilist.userid) "activity" else "message") + putExtra("userId", userId) + } + ContextCompat.startActivity(requireContext(), intent, null) + } private suspend fun getList() { val list = when (type) { @@ -113,14 +106,14 @@ class ActivityFragment : Fragment() { ActivityType.OTHER_USER -> getActivities(userId = userId) ActivityType.ONE -> getActivities(activityId = activityId) } - adapter.addAll(list.map { ActivityItem(it, ::onActivityClick, adapter ,requireActivity()) }) + adapter.addAll(list.map { ActivityItem(it, adapter, ::onActivityClick) }) } private suspend fun getActivities( global: Boolean = false, userId: Int? = null, activityId: Int? = null, - filter:Boolean = false + filter: Boolean = false ): List { val res = Anilist.query.getFeed(userId, global, page, activityId)?.data?.page?.activities page += 1 @@ -141,37 +134,33 @@ class ActivityFragment : Fragment() { } private fun onActivityClick(id: Int, type: String) { - when (type) { - "USER" -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), ProfileActivity::class.java) - .putExtra("userId", id), null - ) - } + val intent = when (type) { + "USER" -> Intent(requireContext(), ProfileActivity::class.java).putExtra("userId", id) + "MEDIA" -> Intent( + requireContext(), + MediaDetailsActivity::class.java + ).putExtra("mediaId", id) - "MEDIA" -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), MediaDetailsActivity::class.java) - .putExtra("mediaId", id), null - ) - } + else -> return } + ContextCompat.startActivity(requireContext(), intent, null) } override fun onResume() { super.onResume() if (this::binding.isInitialized) { binding.root.requestLayout() - } } companion object { - enum class ActivityType { - GLOBAL, USER, OTHER_USER, ONE - } + enum class ActivityType { GLOBAL, USER, OTHER_USER, ONE } - fun newInstance(type: ActivityType, userId: Int? = null, activityId: Int? = null): ActivityFragment { + fun newInstance( + type: ActivityType, + userId: Int? = null, + activityId: Int? = null + ): ActivityFragment { return ActivityFragment().apply { arguments = Bundle().apply { putSerializable("type", type) diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt index 22cf432b..bca86098 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItem.kt @@ -11,7 +11,6 @@ import ani.dantotsu.buildMarkwon import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.api.Activity import ani.dantotsu.databinding.ItemActivityBinding -import ani.dantotsu.home.status.RepliesBottomDialog import ani.dantotsu.loadImage import ani.dantotsu.profile.User import ani.dantotsu.profile.UsersDialogFragment @@ -29,9 +28,8 @@ import kotlinx.coroutines.withContext class ActivityItem( private val activity: Activity, - val clickCallback: (Int, type: String) -> Unit, private val parentAdapter: GroupieAdapter, - private val fragActivity: FragmentActivity + val clickCallback: (Int, type: String) -> Unit, ) : BindableItem() { private lateinit var binding: ItemActivityBinding @@ -54,14 +52,14 @@ class ActivityItem( } binding.activityRepliesContainer.setOnClickListener { RepliesBottomDialog.newInstance(activity.id) - .show(fragActivity.supportFragmentManager, "replies") + .show((context as FragmentActivity).supportFragmentManager, "replies") } binding.replyCount.text = activity.replyCount.toString() binding.activityReplies.setColorFilter(ContextCompat.getColor(binding.root.context, R.color.bg_opp)) binding.activityLikeContainer.setOnLongClickListener { UsersDialogFragment().apply { userList(userList) - show(fragActivity.supportFragmentManager, "dialog") + show((context as FragmentActivity).supportFragmentManager, "dialog") } true } diff --git a/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt index 45122fec..40cb8f96 100644 --- a/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/FeedActivity.kt @@ -51,6 +51,7 @@ class FeedActivity : AppCompatActivity() { binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() } val getOne = intent.getIntExtra("activityId", -1) if (getOne != -1) { navBar.visibility = View.GONE } + binding.notificationViewPager.isUserInputEnabled = false binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne) binding.notificationViewPager.setOffscreenPageLimit(4) binding.notificationViewPager.setCurrentItem(selected, false) @@ -63,13 +64,7 @@ class FeedActivity : AppCompatActivity() { newTab: AnimatedBottomBar.Tab ) { selected = newIndex - binding.notificationViewPager.setCurrentItem(selected, true) - } - }) - binding.notificationViewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - navBar.selectTabAt(position) + binding.notificationViewPager.setCurrentItem(selected, false) } }) } diff --git a/app/src/main/java/ani/dantotsu/home/status/RepliesBottomDialog.kt b/app/src/main/java/ani/dantotsu/profile/activity/RepliesBottomDialog.kt similarity index 90% rename from app/src/main/java/ani/dantotsu/home/status/RepliesBottomDialog.kt rename to app/src/main/java/ani/dantotsu/profile/activity/RepliesBottomDialog.kt index 44a11baf..184c7af9 100644 --- a/app/src/main/java/ani/dantotsu/home/status/RepliesBottomDialog.kt +++ b/app/src/main/java/ani/dantotsu/profile/activity/RepliesBottomDialog.kt @@ -1,4 +1,4 @@ -package ani.dantotsu.home.status +package ani.dantotsu.profile.activity import android.content.Intent import android.os.Bundle @@ -14,7 +14,6 @@ import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.connections.anilist.api.ActivityReply import ani.dantotsu.databinding.BottomSheetRecyclerBinding import ani.dantotsu.profile.ProfileActivity -import ani.dantotsu.profile.activity.ActivityReplyItem import ani.dantotsu.snackString import ani.dantotsu.util.ActivityMarkdownCreator import com.xwray.groupie.GroupieAdapter @@ -72,14 +71,10 @@ class RepliesBottomDialog : BottomSheetDialogFragment() { adapter.update( replies.map { ActivityReplyItem( - it, - activityId, - requireActivity(), - adapter, - clickCallback = { int, _ -> - onClick(int) - } - ) + it, activityId, requireActivity(), adapter, + ) { i, _ -> + onClick(i) + } } ) } else { diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt index e1e77cf3..05412474 100644 --- a/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationActivity.kt @@ -16,11 +16,12 @@ import ani.dantotsu.initActivity import ani.dantotsu.navBarHeight import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager -import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.* +import ani.dantotsu.profile.notification.NotificationFragment.Companion.newInstance import nl.joery.animatedbottombar.AnimatedBottomBar class NotificationActivity : AppCompatActivity() { - private lateinit var binding: ActivityNotificationBinding + lateinit var binding: ActivityNotificationBinding private var selected: Int = 0 lateinit var navBar: AnimatedBottomBar override fun onCreate(savedInstanceState: Bundle?) { @@ -38,8 +39,8 @@ class NotificationActivity : AppCompatActivity() { bottomMargin = navBarHeight } val tabs = listOf( - Pair(R.drawable.ic_round_movie_filter_24, "Media"), Pair(R.drawable.ic_round_person_24, "User"), + Pair(R.drawable.ic_round_movie_filter_24, "Media"), Pair(R.drawable.ic_round_notifications_active_24, "Subs"), Pair(R.drawable.ic_round_comment_24, "Comments") ) @@ -48,8 +49,8 @@ class NotificationActivity : AppCompatActivity() { binding.notificationBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() } val getOne = intent.getIntExtra("activityId", -1) if (getOne != -1) navBar.isVisible = false + binding.notificationViewPager.isUserInputEnabled = false binding.notificationViewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, getOne) - binding.notificationViewPager.setOffscreenPageLimit(4) binding.notificationViewPager.setCurrentItem(selected, false) navBar.selectTabAt(selected) navBar.setOnTabSelectListener(object : AnimatedBottomBar.OnTabSelectListener { @@ -60,13 +61,7 @@ class NotificationActivity : AppCompatActivity() { newTab: AnimatedBottomBar.Tab ) { selected = newIndex - binding.notificationViewPager.setCurrentItem(selected, true) - } - }) - binding.notificationViewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { - override fun onPageSelected(position: Int) { - super.onPageSelected(position) - navBar.selectTabAt(position) + binding.notificationViewPager.setCurrentItem(selected, false) } }) } @@ -84,11 +79,11 @@ class NotificationActivity : AppCompatActivity() { override fun getItemCount(): Int = if (id != -1) 1 else 4 override fun createFragment(position: Int): Fragment = when (position) { - 0 -> NotificationFragment.newInstance(if (id != -1) NotificationType.ONE else NotificationType.MEDIA, id) - 1 -> NotificationFragment.newInstance(NotificationType.USER) - 2 -> NotificationFragment.newInstance(NotificationType.SUBSCRIPTION) - 3 -> NotificationFragment.newInstance(NotificationType.COMMENT) - else -> NotificationFragment.newInstance(NotificationType.MEDIA) + 0 -> newInstance(if (id != -1) ONE else USER, id) + 1 -> newInstance(MEDIA) + 2 -> newInstance(SUBSCRIPTION) + 3 -> newInstance(COMMENT) + else -> newInstance(MEDIA) } } } diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt index 2e6f2d28..c38ec0fb 100644 --- a/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationFragment.kt @@ -20,7 +20,11 @@ import ani.dantotsu.notifications.comment.CommentStore import ani.dantotsu.notifications.subscription.SubscriptionStore import ani.dantotsu.profile.ProfileActivity import ani.dantotsu.profile.activity.FeedActivity -import ani.dantotsu.setBaseline +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.COMMENT +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.MEDIA +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.ONE +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.SUBSCRIPTION +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.USER import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import com.xwray.groupie.GroupieAdapter @@ -29,8 +33,8 @@ import kotlinx.coroutines.launch class NotificationFragment : Fragment() { - private lateinit var type : NotificationType - private var getID : Int = -1 + private lateinit var type: NotificationType + private var getID: Int = -1 private lateinit var binding: FragmentNotificationsBinding private var adapter: GroupieAdapter = GroupieAdapter() private var currentPage = 1 @@ -46,17 +50,17 @@ class NotificationFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - type = arguments?.getSerializableCompat("type") as NotificationType - getID = arguments?.getInt("id") ?: -1 + arguments?.let { + getID = it.getInt("id") + type = it.getSerializableCompat("type") as NotificationType + } binding.notificationRecyclerView.adapter = adapter binding.notificationRecyclerView.layoutManager = LinearLayoutManager(context) binding.notificationProgressBar.isVisible = true - binding.emptyTextView.text = getString(R.string.no_notifications) + binding.emptyTextView.text = getString(R.string.nothing_here) lifecycleScope.launch { getList() - if (adapter.itemCount == 0) { - binding.emptyTextView.isVisible = true - } + binding.notificationProgressBar.isVisible = false } binding.notificationSwipeRefresh.setOnRefreshListener { @@ -85,13 +89,16 @@ class NotificationFragment : Fragment() { private suspend fun getList() { val list = when (type) { - NotificationType.ONE -> getNotificationsFiltered(false) { it.id == getID } - NotificationType.MEDIA -> getNotificationsFiltered(type = true) { it.media != null } - NotificationType.USER -> getNotificationsFiltered { it.media == null } - NotificationType.SUBSCRIPTION -> getSubscriptions() - NotificationType.COMMENT -> getComments() + ONE -> getNotificationsFiltered(false) { it.id == getID } + MEDIA -> getNotificationsFiltered(type = true) { it.media != null } + USER -> getNotificationsFiltered { it.media == null } + SUBSCRIPTION -> getSubscriptions() + COMMENT -> getComments() + } + adapter.addAll(list.map { NotificationItem(it, type, adapter, ::onClick) }) + if (adapter.itemCount == 0) { + binding.emptyTextView.isVisible = true } - adapter.addAll(list.map { NotificationItem(it, ::onClick) }) } private suspend fun getNotificationsFiltered( @@ -112,8 +119,11 @@ class NotificationFragment : Fragment() { PrefName.SubscriptionNotificationStore, null ) ?: listOf() - return list.sortedByDescending { (it.time / 1000L).toInt() } - .filter { it.image != null }.map { + + return list + .sortedByDescending { (it.time / 1000L).toInt() } + .filter { it.image != null } // to remove old data + .map { Notification( it.type, System.currentTimeMillis().toInt(), @@ -158,66 +168,57 @@ class NotificationFragment : Fragment() { !binding.notificationRecyclerView.canScrollVertically(1) } - fun onClick( - id: Int, - optional: Int?, - type: NotificationClickType - ) { - when (type) { - NotificationClickType.USER -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), ProfileActivity::class.java) - .putExtra("userId", id), null - ) + fun onClick(id: Int, optional: Int?, type: NotificationClickType) { + val intent = when (type) { + NotificationClickType.USER -> Intent( + requireContext(), + ProfileActivity::class.java + ).apply { + putExtra("userId", id) } - NotificationClickType.MEDIA -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), MediaDetailsActivity::class.java) - .putExtra("mediaId", id), null - ) + NotificationClickType.MEDIA -> Intent( + requireContext(), + MediaDetailsActivity::class.java + ).apply { + putExtra("mediaId", id) } - NotificationClickType.ACTIVITY -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), FeedActivity::class.java) - .putExtra("activityId", id), null - ) + NotificationClickType.ACTIVITY -> Intent( + requireContext(), + FeedActivity::class.java + ).apply { + putExtra("activityId", id) } - NotificationClickType.COMMENT -> { - ContextCompat.startActivity( - requireContext(), Intent(requireContext(), MediaDetailsActivity::class.java) - .putExtra("FRAGMENT_TO_LOAD", "COMMENTS") - .putExtra("mediaId", id) - .putExtra("commentId", optional ?: -1), - null - ) - + NotificationClickType.COMMENT -> Intent( + requireContext(), + MediaDetailsActivity::class.java + ).apply { + putExtra("FRAGMENT_TO_LOAD", "COMMENTS") + putExtra("mediaId", id) + putExtra("commentId", optional ?: -1) } - NotificationClickType.UNDEFINED -> { - // Do nothing - } + NotificationClickType.UNDEFINED -> null + } + + intent?.let { + ContextCompat.startActivity(requireContext(), it, null) } } + override fun onResume() { super.onResume() if (this::binding.isInitialized) { binding.root.requestLayout() - binding.root.setBaseline((activity as NotificationActivity).navBar) } } companion object { - enum class NotificationClickType { - USER, MEDIA, ACTIVITY, COMMENT, UNDEFINED - } - - enum class NotificationType { - MEDIA, USER, SUBSCRIPTION, COMMENT, ONE - } + enum class NotificationClickType { USER, MEDIA, ACTIVITY, COMMENT, UNDEFINED } + enum class NotificationType { MEDIA, USER, SUBSCRIPTION, COMMENT, ONE } fun newInstance(type: NotificationType, id: Int = -1): NotificationFragment { return NotificationFragment().apply { diff --git a/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt b/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt index f51225ea..61273374 100644 --- a/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt +++ b/app/src/main/java/ani/dantotsu/profile/notification/NotificationItem.kt @@ -8,16 +8,27 @@ import ani.dantotsu.connections.anilist.api.Notification import ani.dantotsu.connections.anilist.api.NotificationType import ani.dantotsu.databinding.ItemNotificationBinding import ani.dantotsu.loadImage -import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationClickType +import ani.dantotsu.notifications.comment.CommentStore +import ani.dantotsu.notifications.subscription.SubscriptionStore import ani.dantotsu.profile.activity.ActivityItemBuilder +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationClickType +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.COMMENT +import ani.dantotsu.profile.notification.NotificationFragment.Companion.NotificationType.SUBSCRIPTION import ani.dantotsu.setAnimation +import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.toPx +import ani.dantotsu.util.customAlertDialog +import com.xwray.groupie.GroupieAdapter import com.xwray.groupie.viewbinding.BindableItem class NotificationItem( private val notification: Notification, - val clickCallback: (Int, Int?, NotificationClickType) -> Unit -) : BindableItem() { + val type: NotificationFragment.Companion.NotificationType, + val parentAdapter: GroupieAdapter, + val clickCallback: (Int, Int?, NotificationClickType) -> Unit, + + ) : BindableItem() { private lateinit var binding: ItemNotificationBinding override fun bind(viewBinding: ItemNotificationBinding, position: Int) { binding = viewBinding @@ -25,6 +36,48 @@ class NotificationItem( setBinding() } + fun dialog() { + when (type) { + COMMENT, SUBSCRIPTION -> { + binding.root.context.customAlertDialog().apply { + setTitle(R.string.delete) + setMessage(ActivityItemBuilder.getContent(notification)) + setPosButton(R.string.yes) { + when (type) { + COMMENT -> { + val list = PrefManager.getNullableVal>( + PrefName.CommentNotificationStore, + null + ) ?: listOf() + val newList = list.filter { it.commentId != notification.commentId } + PrefManager.setVal(PrefName.CommentNotificationStore, newList) + parentAdapter.remove(this@NotificationItem) + + } + + SUBSCRIPTION -> { + val list = PrefManager.getNullableVal>( + PrefName.SubscriptionNotificationStore, + null + ) ?: listOf() + val newList = list.filter { (it.time / 1000L).toInt() != notification.createdAt} + PrefManager.setVal(PrefName.SubscriptionNotificationStore, newList) + parentAdapter.remove(this@NotificationItem) + } + + else -> {} + } + } + setNegButton(R.string.no) + show() + } + } + + else -> {} + } + + } + override fun getLayout(): Int { return R.layout.item_notification } @@ -33,7 +86,11 @@ class NotificationItem( return ItemNotificationBinding.bind(view) } - private fun image(user: Boolean = false, commentNotification: Boolean = false, newRelease: Boolean = false) { + private fun image( + user: Boolean = false, + commentNotification: Boolean = false, + newRelease: Boolean = false + ) { val cover = if (user) notification.user?.bannerImage ?: notification.user?.avatar?.medium else notification.media?.bannerImage @@ -348,6 +405,14 @@ class NotificationItem( } } } + binding.notificationCoverUser.setOnLongClickListener { + dialog() + true + } + binding.notificationBannerImage.setOnLongClickListener { + dialog() + true + } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt index 2c00bdc3..dd11df3a 100644 --- a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt @@ -34,6 +34,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.util.customAlertDialog import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager @@ -177,25 +178,20 @@ class ExtensionsActivity : AppCompatActivity() { entry.name.lowercase().replace("_", " ") .replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() } }.toTypedArray() - val builder = AlertDialog.Builder(this, R.style.MyPopup) val listOrder: String = PrefManager.getVal(PrefName.LangSort) val index = LanguageMapper.Companion.Language.entries.toTypedArray() .indexOfFirst { it.code == listOrder } - builder.setTitle("Language") - builder.setSingleChoiceItems(languageOptions, index) { dialog, i -> - PrefManager.setVal( - PrefName.LangSort, - LanguageMapper.Companion.Language.entries[i].code - ) - val currentFragment = - supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}") - if (currentFragment is SearchQueryHandler) { - currentFragment.notifyDataChanged() + customAlertDialog().apply { + setTitle("Language") + singleChoiceItems(languageOptions, index) { selected -> + PrefManager.setVal(PrefName.LangSort, LanguageMapper.Companion.Language.entries[selected].code) + val currentFragment = supportFragmentManager.findFragmentByTag("f${viewPager.currentItem}") + if (currentFragment is SearchQueryHandler) { + currentFragment.notifyDataChanged() + } } - dialog.dismiss() + show() } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) } binding.settingsContainer.updateLayoutParams { topMargin = statusBarHeight @@ -246,10 +242,10 @@ class ExtensionsActivity : AppCompatActivity() { ) view.repositoryItem.text = item.removePrefix("https://raw.githubusercontent.com") view.repositoryItem.setOnClickListener { - AlertDialog.Builder(this@ExtensionsActivity, R.style.MyPopup) - .setTitle(R.string.rem_repository) - .setMessage(item) - .setPositiveButton(getString(R.string.ok)) { dialog, _ -> + customAlertDialog().apply { + setTitle(R.string.rem_repository) + setMessage(item) + setPosButton(R.string.ok) { val repos = PrefManager.getVal>(prefName).minus(item) PrefManager.setVal(prefName, repos) repoInventory.removeView(view.root) @@ -266,13 +262,10 @@ class ExtensionsActivity : AppCompatActivity() { else -> {} } } - dialog.dismiss() } - .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - } - .create() - .show() + setNegButton(R.string.cancel) + show() + } } view.repositoryItem.setOnLongClickListener { it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -323,20 +316,18 @@ class ExtensionsActivity : AppCompatActivity() { dialogView.repoInventory.apply { getSavedRepositories(this, type) } - val alertDialog = AlertDialog.Builder(this@ExtensionsActivity, R.style.MyPopup) - .setTitle(R.string.edit_repositories) - .setView(dialogView.root) - .setPositiveButton(getString(R.string.add_list)) { _, _ -> - if (!dialogView.repositoryTextBox.text.isNullOrBlank()) - processUserInput(dialogView.repositoryTextBox.text.toString(), type) - } - .setNegativeButton(getString(R.string.close)) { dialog, _ -> - dialog.dismiss() - } - .create() processEditorAction(dialogView.repositoryTextBox, type) - alertDialog.show() - alertDialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(R.string.edit_repositories) + setCustomView(dialogView.root) + setPosButton(R.string.add_list) { + if (!dialogView.repositoryTextBox.text.isNullOrBlank()) { + processUserInput(dialogView.repositoryTextBox.text.toString(), type) + } + } + setNegButton(R.string.close) + show() + } } } } diff --git a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt index 49656f55..98a248e2 100644 --- a/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/FAQActivity.kt @@ -50,6 +50,11 @@ class FAQActivity : AppCompatActivity() { currContext()?.getString(R.string.question_5) ?: "", currContext()?.getString(R.string.answer_5) ?: "" ), + Triple( + R.drawable.ic_anilist, + currContext()?.getString(R.string.question_18) ?: "", + currContext()?.getString(R.string.answer_18) ?: "" + ), Triple( R.drawable.ic_anilist, currContext()?.getString(R.string.question_6) ?: "", @@ -60,6 +65,11 @@ class FAQActivity : AppCompatActivity() { currContext()?.getString(R.string.question_7) ?: "", currContext()?.getString(R.string.answer_7) ?: "" ), + Triple( + R.drawable.ic_round_magnet_24, + currContext()?.getString(R.string.question_19) ?: "", + currContext()?.getString(R.string.answer_19) ?: "" + ), Triple( R.drawable.ic_round_lock_open_24, currContext()?.getString(R.string.question_9) ?: "", diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt index 2e6d650c..4d3350e3 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledAnimeExtensionsFragment.kt @@ -25,13 +25,15 @@ import androidx.viewpager2.widget.ViewPager2 import ani.dantotsu.R import ani.dantotsu.connections.crashlytics.CrashlyticsInterface import ani.dantotsu.databinding.FragmentExtensionsBinding -import ani.dantotsu.others.LanguageMapper +import ani.dantotsu.others.LanguageMapper.Companion.getLanguageName import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.settings.extensionprefs.AnimeSourcePreferencesFragment import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog + import com.google.android.material.tabs.TabLayout import com.google.android.material.textfield.TextInputLayout import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource @@ -72,16 +74,15 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { if (allSettings.isNotEmpty()) { var selectedSetting = allSettings[0] if (allSettings.size > 1) { - val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) } + val names = allSettings.map { getLanguageName(it.lang) } .toTypedArray() var selectedIndex = 0 - val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) - .setTitle("Select a Source") - .setSingleChoiceItems(names, selectedIndex) { dialog, which -> + requireContext().customAlertDialog().apply { + setTitle("Select a Source") + singleChoiceItems(names, selectedIndex) { which -> itemSelected = true selectedIndex = which selectedSetting = allSettings[selectedIndex] - dialog.dismiss() val fragment = AnimeSourcePreferencesFragment().getInstance(selectedSetting.id) { @@ -93,13 +94,13 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { .addToBackStack(null) .commit() } - .setOnDismissListener { + onDismiss { if (!itemSelected) { changeUIVisibility(true) } } - .show() - dialog.window?.setDimAmount(0.8f) + show() + } } else { // If there's only one setting, proceed with the fragment transaction val fragment = @@ -295,7 +296,7 @@ class InstalledAnimeExtensionsFragment : Fragment(), SearchQueryHandler { override fun onBindViewHolder(holder: ViewHolder, position: Int) { val extension = getItem(position) val nsfw = if (extension.isNsfw) "(18+)" else "" - val lang = LanguageMapper.getLanguageName(extension.lang) + val lang = getLanguageName(extension.lang) holder.extensionNameTextView.text = extension.name val versionText = "$lang ${extension.versionName} $nsfw" holder.extensionVersionTextView.text = versionText diff --git a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt index 0a66c648..c11ec223 100644 --- a/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt +++ b/app/src/main/java/ani/dantotsu/settings/InstalledMangaExtensionsFragment.kt @@ -34,6 +34,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.util.Logger +import ani.dantotsu.util.customAlertDialog import com.google.android.material.tabs.TabLayout import com.google.android.material.textfield.TextInputLayout import eu.kanade.tachiyomi.data.notification.Notifications @@ -74,13 +75,12 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { val names = allSettings.map { LanguageMapper.getLanguageName(it.lang) } .toTypedArray() var selectedIndex = 0 - val dialog = AlertDialog.Builder(requireContext(), R.style.MyPopup) - .setTitle("Select a Source") - .setSingleChoiceItems(names, selectedIndex) { dialog, which -> + requireContext().customAlertDialog().apply { + setTitle("Select a Source") + singleChoiceItems(names, selectedIndex) { which -> itemSelected = true selectedIndex = which selectedSetting = allSettings[selectedIndex] - dialog.dismiss() // Move the fragment transaction here val fragment = @@ -93,13 +93,13 @@ class InstalledMangaExtensionsFragment : Fragment(), SearchQueryHandler { .addToBackStack(null) .commit() } - .setOnDismissListener { + onDismiss { if (!itemSelected) { changeUIVisibility(true) } } - .show() - dialog.window?.setDimAmount(0.8f) + show() + } } else { // If there's only one setting, proceed with the fragment transaction val fragment = diff --git a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt index 9006228a..e3999509 100644 --- a/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/PlayerSettingsActivity.kt @@ -28,6 +28,7 @@ import ani.dantotsu.snackString import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import ani.dantotsu.toast +import ani.dantotsu.util.customAlertDialog import com.google.android.material.slider.Slider.OnChangeListener import kotlin.math.roundToInt @@ -100,20 +101,19 @@ class PlayerSettingsActivity : AppCompatActivity() { R.string.default_playback_speed, speedsName[PrefManager.getVal(PrefName.DefaultSpeed)] ) - val speedDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.default_speed)) binding.playerSettingsSpeed.setOnClickListener { - val dialog = - speedDialog.setSingleChoiceItems( + customAlertDialog().apply { + setTitle(getString(R.string.default_speed)) + singleChoiceItems( speedsName, PrefManager.getVal(PrefName.DefaultSpeed) - ) { dialog, i -> + ) { i -> PrefManager.setVal(PrefName.DefaultSpeed, i) binding.playerSettingsSpeed.text = getString(R.string.default_playback_speed, speedsName[i]) - dialog.dismiss() - }.show() - dialog.window?.setDimAmount(0.8f) + } + show() + } } binding.playerSettingsCursedSpeeds.isChecked = PrefManager.getVal(PrefName.CursedSpeeds) @@ -278,17 +278,17 @@ class PlayerSettingsActivity : AppCompatActivity() { } val resizeModes = arrayOf("Original", "Zoom", "Stretch") - val resizeDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.default_resize_mode)) binding.playerResizeMode.setOnClickListener { - val dialog = resizeDialog.setSingleChoiceItems( - resizeModes, - PrefManager.getVal(PrefName.Resize) - ) { dialog, count -> - PrefManager.setVal(PrefName.Resize, count) - dialog.dismiss() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.default_resize_mode)) + singleChoiceItems( + resizeModes, + PrefManager.getVal(PrefName.Resize) + ) { count -> + PrefManager.setVal(PrefName.Resize, count) + } + show() + } } fun toggleSubOptions(isChecked: Boolean) { @@ -332,18 +332,18 @@ class PlayerSettingsActivity : AppCompatActivity() { "Blue", "Magenta" ) - val primaryColorDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.primary_sub_color)) binding.videoSubColorPrimary.setOnClickListener { - val dialog = primaryColorDialog.setSingleChoiceItems( - colorsPrimary, - PrefManager.getVal(PrefName.PrimaryColor) - ) { dialog, count -> - PrefManager.setVal(PrefName.PrimaryColor, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.primary_sub_color)) + singleChoiceItems( + colorsPrimary, + PrefManager.getVal(PrefName.PrimaryColor) + ) { count -> + PrefManager.setVal(PrefName.PrimaryColor, count) + updateSubPreview() + } + show() + } } val colorsSecondary = arrayOf( "Black", @@ -359,32 +359,32 @@ class PlayerSettingsActivity : AppCompatActivity() { "Magenta", "Transparent" ) - val secondaryColorDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.outline_sub_color)) binding.videoSubColorSecondary.setOnClickListener { - val dialog = secondaryColorDialog.setSingleChoiceItems( - colorsSecondary, - PrefManager.getVal(PrefName.SecondaryColor) - ) { dialog, count -> - PrefManager.setVal(PrefName.SecondaryColor, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.outline_sub_color)) + singleChoiceItems( + colorsSecondary, + PrefManager.getVal(PrefName.SecondaryColor) + ) { count -> + PrefManager.setVal(PrefName.SecondaryColor, count) + updateSubPreview() + } + show() + } } val typesOutline = arrayOf("Outline", "Shine", "Drop Shadow", "None") - val outlineDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.outline_type)) binding.videoSubOutline.setOnClickListener { - val dialog = outlineDialog.setSingleChoiceItems( - typesOutline, - PrefManager.getVal(PrefName.Outline) - ) { dialog, count -> - PrefManager.setVal(PrefName.Outline, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.outline_type)) + singleChoiceItems( + typesOutline, + PrefManager.getVal(PrefName.Outline) + ) { count -> + PrefManager.setVal(PrefName.Outline, count) + updateSubPreview() + } + show() + } } val colorsSubBackground = arrayOf( "Transparent", @@ -400,18 +400,18 @@ class PlayerSettingsActivity : AppCompatActivity() { "Blue", "Magenta" ) - val subBackgroundDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.sub_background_color_select)) binding.videoSubColorBackground.setOnClickListener { - val dialog = subBackgroundDialog.setSingleChoiceItems( - colorsSubBackground, - PrefManager.getVal(PrefName.SubBackground) - ) { dialog, count -> - PrefManager.setVal(PrefName.SubBackground, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.sub_background_color_select)) + singleChoiceItems( + colorsSubBackground, + PrefManager.getVal(PrefName.SubBackground) + ) { count -> + PrefManager.setVal(PrefName.SubBackground, count) + updateSubPreview() + } + show() + } } val colorsSubWindow = arrayOf( @@ -428,18 +428,18 @@ class PlayerSettingsActivity : AppCompatActivity() { "Blue", "Magenta" ) - val subWindowDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.sub_window_color_select)) binding.videoSubColorWindow.setOnClickListener { - val dialog = subWindowDialog.setSingleChoiceItems( - colorsSubWindow, - PrefManager.getVal(PrefName.SubWindow) - ) { dialog, count -> - PrefManager.setVal(PrefName.SubWindow, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.sub_window_color_select)) + singleChoiceItems( + colorsSubWindow, + PrefManager.getVal(PrefName.SubWindow) + ) { count -> + PrefManager.setVal(PrefName.SubWindow, count) + updateSubPreview() + } + show() + } } binding.videoSubAlpha.value = PrefManager.getVal(PrefName.SubAlpha) @@ -459,18 +459,18 @@ class PlayerSettingsActivity : AppCompatActivity() { "Levenim MT Bold", "Blocky" ) - val fontDialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.subtitle_font)) binding.videoSubFont.setOnClickListener { - val dialog = fontDialog.setSingleChoiceItems( - fonts, - PrefManager.getVal(PrefName.Font) - ) { dialog, count -> - PrefManager.setVal(PrefName.Font, count) - dialog.dismiss() - updateSubPreview() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.subtitle_font)) + singleChoiceItems( + fonts, + PrefManager.getVal(PrefName.Font) + ) { count -> + PrefManager.setVal(PrefName.Font, count) + updateSubPreview() + } + show() + } } binding.subtitleFontSize.setText(PrefManager.getVal(PrefName.FontSize).toString()) binding.subtitleFontSize.setOnEditorActionListener { _, actionId, _ -> diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt index 2022fac1..7e58e870 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsActivity.kt @@ -23,12 +23,12 @@ import ani.dantotsu.databinding.ActivitySettingsBinding import ani.dantotsu.initActivity import ani.dantotsu.navBarHeight import ani.dantotsu.openLinkInBrowser -import ani.dantotsu.openLinkInYouTube import ani.dantotsu.others.AppUpdater import ani.dantotsu.others.CustomBottomDialog import ani.dantotsu.pop import ani.dantotsu.setSafeOnClickListener import ani.dantotsu.settings.saving.PrefManager +import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.snackString import ani.dantotsu.startMainActivity import ani.dantotsu.statusBarHeight @@ -217,10 +217,14 @@ class SettingsActivity : AppCompatActivity() { settingsLogo.setSafeOnClickListener { cursedCounter++ (settingsLogo.drawable as Animatable).start() - if (cursedCounter % 7 == 0) { - toast(R.string.you_cursed) - openLinkInYouTube(getString(R.string.cursed_yt)) - //PrefManager.setVal(PrefName.ImageUrl, !PrefManager.getVal(PrefName.ImageUrl, false)) + if (cursedCounter % 16 == 0) { + val oldVal: Boolean = PrefManager.getVal(PrefName.OC) + if (!oldVal) { + toast(R.string.omega_cursed) + } else { + toast(R.string.omega_freed) + } + PrefManager.setVal(PrefName.OC, !oldVal) } else { snackString(array[(Math.random() * array.size).toInt()], context) } diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt index c8dc29d5..dea8666c 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsCommonActivity.kt @@ -20,6 +20,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.databinding.ActivitySettingsCommonBinding +import ani.dantotsu.databinding.DialogSetPasswordBinding import ani.dantotsu.databinding.DialogUserAgentBinding import ani.dantotsu.download.DownloadsManager import ani.dantotsu.initActivity @@ -37,6 +38,7 @@ import ani.dantotsu.themes.ThemeManager import ani.dantotsu.toast import ani.dantotsu.util.LauncherWrapper import ani.dantotsu.util.StoragePermissions +import ani.dantotsu.util.customAlertDialog import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -160,18 +162,16 @@ class SettingsCommonActivity : AppCompatActivity() { icon = R.drawable.ic_download_24, onClick = { val managers = arrayOf("Default", "1DM", "ADM") - val downloadManagerDialog = - AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.download_manager) - var downloadManager: Int = PrefManager.getVal(PrefName.DownloadManager) - val dialog = downloadManagerDialog.setSingleChoiceItems( - managers, downloadManager - ) { dialog, count -> - downloadManager = count - PrefManager.setVal(PrefName.DownloadManager, downloadManager) - dialog.dismiss() - }.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.download_manager)) + singleChoiceItems( + managers, + PrefManager.getVal(PrefName.DownloadManager) + ) { count -> + PrefManager.setVal(PrefName.DownloadManager, count) + } + show() + } } ), Settings( @@ -180,90 +180,67 @@ class SettingsCommonActivity : AppCompatActivity() { desc = getString(R.string.app_lock_desc), icon = R.drawable.ic_round_lock_open_24, onClick = { - val passwordDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.app_lock) - .setView(R.layout.dialog_set_password) - .setPositiveButton(R.string.ok) { dialog, _ -> - val passwordInput = - (dialog as AlertDialog).findViewById(R.id.passwordInput) - val confirmPasswordInput = - dialog.findViewById(R.id.confirmPasswordInput) - val forgotPasswordCheckbox = - dialog.findViewById(R.id.forgotPasswordCheckbox) - if (forgotPasswordCheckbox?.isChecked == true) { + customAlertDialog().apply { + val view = DialogSetPasswordBinding.inflate(layoutInflater) + setTitle(R.string.app_lock) + setCustomView(view.root) + setPosButton(R.string.ok) { + if (view.forgotPasswordCheckbox.isChecked) { PrefManager.setVal(PrefName.OverridePassword, true) } - - val password = passwordInput?.text.toString() - val confirmPassword = confirmPasswordInput?.text.toString() - + val password = view.passwordInput.text.toString() + val confirmPassword = view.confirmPasswordInput.text.toString() if (password == confirmPassword && password.isNotEmpty()) { PrefManager.setVal(PrefName.AppPassword, password) - if (dialog.findViewById(R.id.biometricCheckbox)?.isChecked == true) { + if (view.biometricCheckbox.isChecked) { val canBiometricPrompt = BiometricManager.from(applicationContext) - .canAuthenticate( - BiometricManager.Authenticators.BIOMETRIC_WEAK - ) == BiometricManager.BIOMETRIC_SUCCESS + .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS + if (canBiometricPrompt) { val biometricPrompt = - BiometricPromptUtils.createBiometricPrompt( - this@SettingsCommonActivity - ) { _ -> + BiometricPromptUtils.createBiometricPrompt(this@SettingsCommonActivity) { _ -> val token = UUID.randomUUID().toString() PrefManager.setVal( PrefName.BiometricToken, token ) toast(R.string.success) - dialog.dismiss() } val promptInfo = - BiometricPromptUtils.createPromptInfo( - this@SettingsCommonActivity - ) + BiometricPromptUtils.createPromptInfo(this@SettingsCommonActivity) biometricPrompt.authenticate(promptInfo) } + } else { PrefManager.setVal(PrefName.BiometricToken, "") toast(R.string.success) - dialog.dismiss() } } else { toast(R.string.password_mismatch) } } - .setNegativeButton(R.string.cancel) { dialog, _ -> - dialog.dismiss() - } - .setNeutralButton(R.string.remove) { dialog, _ -> + setNegButton(R.string.cancel) + setNeutralButton(R.string.remove){ PrefManager.setVal(PrefName.AppPassword, "") PrefManager.setVal(PrefName.BiometricToken, "") PrefManager.setVal(PrefName.OverridePassword, false) toast(R.string.success) - dialog.dismiss() } - .create() - - passwordDialog.window?.setDimAmount(0.8f) - passwordDialog.setOnShowListener { - passwordDialog.findViewById(R.id.passwordInput) - ?.requestFocus() - val biometricCheckbox = - passwordDialog.findViewById(R.id.biometricCheckbox) - val forgotPasswordCheckbox = - passwordDialog.findViewById(R.id.forgotPasswordCheckbox) - val canAuthenticate = - BiometricManager.from(applicationContext).canAuthenticate( - BiometricManager.Authenticators.BIOMETRIC_WEAK - ) == BiometricManager.BIOMETRIC_SUCCESS - biometricCheckbox?.isVisible = canAuthenticate - biometricCheckbox?.isChecked = - PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty() - forgotPasswordCheckbox?.isChecked = - PrefManager.getVal(PrefName.OverridePassword) + setOnShowListener { + view.passwordInput.requestFocus() + val canAuthenticate = + BiometricManager.from(applicationContext).canAuthenticate( + BiometricManager.Authenticators.BIOMETRIC_WEAK + ) == BiometricManager.BIOMETRIC_SUCCESS + view.biometricCheckbox.isVisible = canAuthenticate + view.biometricCheckbox.isChecked = + PrefManager.getVal(PrefName.BiometricToken, "").isNotEmpty() + view.forgotPasswordCheckbox.isChecked = + PrefManager.getVal(PrefName.OverridePassword) + } + show() } - passwordDialog.show() } ), @@ -328,10 +305,10 @@ class SettingsCommonActivity : AppCompatActivity() { desc = getString(R.string.change_download_location_desc), icon = R.drawable.ic_round_source_24, onClick = { - val dialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.change_download_location) - .setMessage(R.string.download_location_msg) - .setPositiveButton(R.string.ok) { dialog, _ -> + context.customAlertDialog().apply{ + setTitle(R.string.change_download_location) + setMessage(R.string.download_location_msg) + setPosButton(R.string.ok){ val oldUri = PrefManager.getVal(PrefName.DownloadsDir) launcher.registerForCallback { success -> if (success) { @@ -354,12 +331,10 @@ class SettingsCommonActivity : AppCompatActivity() { } } launcher.launch() - dialog.dismiss() - }.setNeutralButton(R.string.cancel) { dialog, _ -> - dialog.dismiss() - }.create() - dialog.window?.setDimAmount(0.8f) - dialog.show() + } + setNegButton(R.string.cancel) + show() + } } ), Settings( @@ -372,6 +347,17 @@ class SettingsCommonActivity : AppCompatActivity() { PrefManager.setVal(PrefName.ContinueMedia, isChecked) } ), + Settings( + type = 2, + name = getString(R.string.hide_private), + desc = getString(R.string.hide_private_desc), + icon = R.drawable.ic_round_remove_red_eye_24, + isChecked = PrefManager.getVal(PrefName.HidePrivate), + switch = { isChecked, _ -> + PrefManager.setVal(PrefName.HidePrivate, isChecked) + restartApp() + } + ), Settings( type = 2, name = getString(R.string.search_source_list), diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt index abf742ba..a054b89a 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt @@ -81,11 +81,11 @@ class SettingsExtensionsActivity : AppCompatActivity() { view.repositoryItem.text = item.removePrefix("https://raw.githubusercontent.com/") view.repositoryItem.setOnClickListener { - AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.rem_repository).setMessage(item) - .setPositiveButton(getString(R.string.ok)) { dialog, _ -> - val repos = - PrefManager.getVal>(repoList).minus(item) + context.customAlertDialog().apply { + setTitle(R.string.rem_repository) + setMessage(item) + setPosButton(R.string.ok) { + val repos = PrefManager.getVal>(repoList).minus(item) PrefManager.setVal(repoList, repos) setExtensionOutput(repoInventory, type) CoroutineScope(Dispatchers.IO).launch { @@ -93,18 +93,16 @@ class SettingsExtensionsActivity : AppCompatActivity() { MediaType.ANIME -> { animeExtensionManager.findAvailableExtensions() } - MediaType.MANGA -> { mangaExtensionManager.findAvailableExtensions() } - else -> {} } } - dialog.dismiss() - }.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - }.create().show() + } + setNegButton(R.string.cancel) + show() + } } view.repositoryItem.setOnLongClickListener { it.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) @@ -209,27 +207,27 @@ class SettingsExtensionsActivity : AppCompatActivity() { val editText = dialogView.userAgentTextBox.apply { hint = getString(R.string.manga_add_repository) } - val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.manga_add_repository).setView(dialogView.root) - .setPositiveButton(getString(R.string.ok)) { dialog, _ -> + context.customAlertDialog().apply { + setTitle(R.string.manga_add_repository) + setCustomView(dialogView.root) + setPosButton(R.string.ok) { if (!editText.text.isNullOrBlank()) processUserInput( editText.text.toString(), MediaType.MANGA, it.attachView ) - dialog.dismiss() - }.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - }.create() + } + setNegButton(R.string.cancel) + attach { dialog -> + processEditorAction( + dialog, + editText, + MediaType.MANGA, + it.attachView + ) + } + }.show() - processEditorAction( - alertDialog, - editText, - MediaType.MANGA, - it.attachView - ) - alertDialog.show() - alertDialog.window?.setDimAmount(0.8f) }, attach = { setExtensionOutput(it.attachView, MediaType.MANGA) @@ -258,24 +256,18 @@ class SettingsExtensionsActivity : AppCompatActivity() { val dialogView = DialogUserAgentBinding.inflate(layoutInflater) val editText = dialogView.userAgentTextBox editText.setText(PrefManager.getVal(PrefName.DefaultUserAgent)) - val alertDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.user_agent).setView(dialogView.root) - .setPositiveButton(getString(R.string.ok)) { dialog, _ -> - PrefManager.setVal( - PrefName.DefaultUserAgent, - editText.text.toString() - ) - dialog.dismiss() - }.setNeutralButton(getString(R.string.reset)) { dialog, _ -> + context.customAlertDialog().apply { + setTitle(R.string.user_agent) + setCustomView(dialogView.root) + setPosButton(R.string.ok) { + PrefManager.setVal(PrefName.DefaultUserAgent, editText.text.toString()) + } + setNeutralButton(R.string.reset) { PrefManager.removeVal(PrefName.DefaultUserAgent) editText.setText("") - dialog.dismiss() - }.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - }.create() - - alertDialog.show() - alertDialog.window?.setDimAmount(0.8f) + } + setNegButton(R.string.cancel) + }.show() } ), Settings( diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt index b97dfa4c..c7a9b1e6 100644 --- a/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/SettingsNotificationActivity.kt @@ -25,6 +25,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.util.customAlertDialog import java.util.Locale class SettingsNotificationActivity : AppCompatActivity() { @@ -77,26 +78,16 @@ class SettingsNotificationActivity : AppCompatActivity() { desc = getString(R.string.subscriptions_info), icon = R.drawable.ic_round_notifications_none_24, onClick = { - val speedDialog = AlertDialog.Builder(context, R.style.MyPopup) - .setTitle(R.string.subscriptions_checking_time) - val dialog = - speedDialog.setSingleChoiceItems(timeNames, curTime) { dialog, i -> + context.customAlertDialog().apply { + setTitle(R.string.subscriptions_checking_time) + singleChoiceItems(timeNames, curTime) { i -> curTime = i - it.settingsTitle.text = - getString( - R.string.subscriptions_checking_time_s, - timeNames[i] - ) - PrefManager.setVal( - PrefName.SubscriptionNotificationInterval, - curTime - ) - dialog.dismiss() - TaskScheduler.create( - context, PrefManager.getVal(PrefName.UseAlarmManager) - ).scheduleAllTasks(context) - }.show() - dialog.window?.setDimAmount(0.8f) + it.settingsTitle.text = getString(R.string.subscriptions_checking_time_s, timeNames[i]) + PrefManager.setVal(PrefName.SubscriptionNotificationInterval, curTime) + TaskScheduler.create(context, PrefManager.getVal(PrefName.UseAlarmManager)).scheduleAllTasks(context) + } + show() + } }, onLongClick = { TaskScheduler.create( diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt index bb8c0396..bb60cd4e 100644 --- a/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt +++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt @@ -9,31 +9,25 @@ import ani.dantotsu.loadImage import ani.dantotsu.media.MediaDetailsActivity import ani.dantotsu.notifications.subscription.SubscriptionHelper import com.xwray.groupie.GroupieAdapter -import com.xwray.groupie.Item import com.xwray.groupie.viewbinding.BindableItem class SubscriptionItem( val id: Int, private val media: SubscriptionHelper.Companion.SubscribeMedia, - private val adapter: GroupieAdapter + private val adapter: GroupieAdapter, + private val onItemRemoved: (Int) -> Unit ) : BindableItem() { private lateinit var binding: ItemSubscriptionBinding - override fun bind(p0: ItemSubscriptionBinding, p1: Int) { - val context = p0.root.context - binding = p0 - val parserName = if (media.isAnime) - SubscriptionHelper.getAnimeParser(media.id).name - else - SubscriptionHelper.getMangaParser(media.id).name - val mediaName = media.name - val showName = "$mediaName ($parserName)" - binding.subscriptionName.text = showName + + override fun bind(viewBinding: ItemSubscriptionBinding, position: Int) { + binding = viewBinding + val context = binding.root.context + + binding.subscriptionName.text = media.name binding.root.setOnClickListener { ContextCompat.startActivity( context, - Intent(context, MediaDetailsActivity::class.java).putExtra( - "mediaId", media.id - ), + Intent(context, MediaDetailsActivity::class.java).putExtra("mediaId", media.id), null ) } @@ -41,14 +35,11 @@ class SubscriptionItem( binding.deleteSubscription.setOnClickListener { SubscriptionHelper.deleteSubscription(id, true) adapter.remove(this) + onItemRemoved(id) } } - override fun getLayout(): Int { - return R.layout.item_subscription - } + override fun getLayout(): Int = R.layout.item_subscription - override fun initializeViewBinding(p0: View): ItemSubscriptionBinding { - return ItemSubscriptionBinding.bind(p0) - } + override fun initializeViewBinding(view: View): ItemSubscriptionBinding = ItemSubscriptionBinding.bind(view) } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt new file mode 100644 index 00000000..6740b148 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionSource.kt @@ -0,0 +1,112 @@ +package ani.dantotsu.settings + +import android.app.AlertDialog +import android.content.Context +import android.graphics.drawable.Drawable +import android.view.View +import android.view.ViewGroup +import ani.dantotsu.R +import ani.dantotsu.databinding.ItemExtensionBinding +import ani.dantotsu.notifications.subscription.SubscriptionHelper +import ani.dantotsu.util.customAlertDialog +import com.xwray.groupie.GroupieAdapter +import com.xwray.groupie.viewbinding.BindableItem + +class SubscriptionSource( + private val parserName: String, + private val subscriptions: MutableList, + private val adapter: GroupieAdapter, + private var parserIcon: Drawable? = null, + private val onGroupRemoved: (SubscriptionSource) -> Unit +) : BindableItem() { + private lateinit var binding: ItemExtensionBinding + private var isExpanded = false + + override fun bind(viewBinding: ItemExtensionBinding, position: Int) { + binding = viewBinding + binding.extensionNameTextView.text = parserName + updateSubscriptionCount() + binding.extensionSubscriptions.visibility = View.VISIBLE + binding.root.setOnClickListener { + isExpanded = !isExpanded + toggleSubscriptions() + } + binding.root.setOnLongClickListener { + showRemoveAllSubscriptionsDialog(it.context) + true + } + binding.extensionIconImageView.visibility = View.VISIBLE + val layoutParams = binding.extensionIconImageView.layoutParams as ViewGroup.MarginLayoutParams + layoutParams.leftMargin = 28 + binding.extensionIconImageView.layoutParams = layoutParams + + parserIcon?.let { + binding.extensionIconImageView.setImageDrawable(it) + } ?: run { + binding.extensionIconImageView.setImageResource(R.drawable.control_background_40dp) + } + + binding.extensionPinImageView.visibility = View.GONE + binding.extensionVersionTextView.visibility = View.GONE + binding.closeTextView.visibility = View.GONE + binding.settingsImageView.visibility = View.GONE + } + + private fun updateSubscriptionCount() { + binding.subscriptionCount.text = subscriptions.size.toString() + binding.subscriptionCount.visibility = if (subscriptions.isEmpty()) View.GONE else View.VISIBLE + } + + private fun showRemoveAllSubscriptionsDialog(context: Context) { + context.customAlertDialog().apply{ + setTitle(R.string.remove_all_subscriptions) + setMessage(R.string.remove_all_subscriptions_desc, parserName) + setPosButton(R.string.ok) { removeAllSubscriptions() } + setNegButton(R.string.cancel) + show() + } + } + + private fun removeAllSubscriptions() { + subscriptions.forEach { subscription -> + SubscriptionHelper.deleteSubscription(subscription.id, false) + } + if (isExpanded) { + val startPosition = adapter.getAdapterPosition(this) + 1 + repeat(subscriptions.size) { + adapter.removeGroupAtAdapterPosition(startPosition) + } + } + subscriptions.clear() + onGroupRemoved(this) + } + + private fun removeSubscription(id: Any?) { + subscriptions.removeAll { it.id == id } + updateSubscriptionCount() + if (subscriptions.isEmpty()) { + onGroupRemoved(this) + } else { + adapter.notifyItemChanged(adapter.getAdapterPosition(this)) + } + } + + private fun toggleSubscriptions() { + val startPosition = adapter.getAdapterPosition(this) + 1 + if (isExpanded) { + subscriptions.forEachIndexed { index, subscribeMedia -> + adapter.add(startPosition + index, SubscriptionItem(subscribeMedia.id, subscribeMedia, adapter) { removedId -> + removeSubscription(removedId) + }) + } + } else { + repeat(subscriptions.size) { + adapter.removeGroupAtAdapterPosition(startPosition) + } + } + } + + override fun getLayout(): Int = R.layout.item_extension + + override fun initializeViewBinding(view: View): ItemExtensionBinding = ItemExtensionBinding.bind(view) +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt index 2b833114..93fd58db 100644 --- a/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt +++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionsBottomDialog.kt @@ -1,5 +1,6 @@ package ani.dantotsu.settings +import android.graphics.drawable.Drawable import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -9,13 +10,21 @@ import ani.dantotsu.BottomSheetDialogFragment import ani.dantotsu.R import ani.dantotsu.databinding.BottomSheetRecyclerBinding import ani.dantotsu.notifications.subscription.SubscriptionHelper +import ani.dantotsu.parsers.novel.NovelExtensionManager import com.xwray.groupie.GroupieAdapter +import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager +import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get class SubscriptionsBottomDialog : BottomSheetDialogFragment() { private var _binding: BottomSheetRecyclerBinding? = null private val binding get() = _binding!! private val adapter: GroupieAdapter = GroupieAdapter() private var subscriptions: Map = mapOf() + private val animeExtension: AnimeExtensionManager = Injekt.get() + private val mangaExtensions: MangaExtensionManager = Injekt.get() + private val novelExtensions: NovelExtensionManager = Injekt.get() override fun onCreateView( inflater: LayoutInflater, @@ -36,8 +45,33 @@ class SubscriptionsBottomDialog : BottomSheetDialogFragment() { val context = requireContext() binding.title.text = context.getString(R.string.subscriptions) binding.replyButton.visibility = View.GONE - subscriptions.forEach { (id, media) -> - adapter.add(SubscriptionItem(id, media, adapter)) + + val groupedSubscriptions = subscriptions.values.groupBy { + if (it.isAnime) SubscriptionHelper.getAnimeParser(it.id).name + else SubscriptionHelper.getMangaParser(it.id).name + } + + groupedSubscriptions.forEach { (parserName, mediaList) -> + adapter.add(SubscriptionSource( + parserName, + mediaList.toMutableList(), + adapter, + getParserIcon(parserName) + ) { group -> + adapter.remove(group) + }) + } + } + + private fun getParserIcon(parserName: String): Drawable? { + return when { + animeExtension.installedExtensionsFlow.value.any { it.name == parserName } -> + animeExtension.installedExtensionsFlow.value.find { it.name == parserName }?.icon + mangaExtensions.installedExtensionsFlow.value.any { it.name == parserName } -> + mangaExtensions.installedExtensionsFlow.value.find { it.name == parserName }?.icon + novelExtensions.installedExtensionsFlow.value.any { it.name == parserName } -> + novelExtensions.installedExtensionsFlow.value.find { it.name == parserName }?.icon + else -> null } } diff --git a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt index 8d1a9582..08b6345e 100644 --- a/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt +++ b/app/src/main/java/ani/dantotsu/settings/UserInterfaceSettingsActivity.kt @@ -14,6 +14,7 @@ import ani.dantotsu.settings.saving.PrefManager import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager +import ani.dantotsu.util.customAlertDialog class UserInterfaceSettingsActivity : AppCompatActivity() { lateinit var binding: ActivityUserInterfaceSettingsBinding @@ -38,20 +39,23 @@ class UserInterfaceSettingsActivity : AppCompatActivity() { binding.uiSettingsHomeLayout.setOnClickListener { val set = PrefManager.getVal>(PrefName.HomeLayout).toMutableList() val views = resources.getStringArray(R.array.home_layouts) - val dialog = AlertDialog.Builder(this, R.style.MyPopup) - .setTitle(getString(R.string.home_layout_show)).apply { - setMultiChoiceItems( - views, - PrefManager.getVal>(PrefName.HomeLayout).toBooleanArray() - ) { _, i, value -> - set[i] = value + customAlertDialog().apply { + setTitle(getString(R.string.home_layout_show)) + multiChoiceItems( + items = views, + checkedItems = PrefManager.getVal>(PrefName.HomeLayout).toBooleanArray() + ) { selectedItems -> + for (i in selectedItems.indices) { + set[i] = selectedItems[i] } - setPositiveButton("Done") { _, _ -> - PrefManager.setVal(PrefName.HomeLayout, set) - restartApp() - } - }.show() - dialog.window?.setDimAmount(0.8f) + } + setPosButton(R.string.ok) { + PrefManager.setVal(PrefName.HomeLayout, set) + restartApp() + } + show() + } + } binding.uiSettingsSmallView.isChecked = PrefManager.getVal(PrefName.SmallView) diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index fb71c0c1..2760d714 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -23,6 +23,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files CheckUpdate(Pref(Location.General, Boolean::class, true)), VerboseLogging(Pref(Location.General, Boolean::class, false)), DohProvider(Pref(Location.General, Int::class, 0)), + HidePrivate(Pref(Location.General, Boolean::class, false)), DefaultUserAgent( Pref( Location.General, @@ -196,6 +197,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files SubscriptionNotificationStore(Pref(Location.Irrelevant, List::class, listOf())), UnreadCommentNotifications(Pref(Location.Irrelevant, Int::class, 0)), DownloadsDir(Pref(Location.Irrelevant, String::class, "")), + OC(Pref(Location.Irrelevant, Boolean::class, false)), RefreshStatus(Pref(Location.Irrelevant, Boolean::class, false)), //Protected diff --git a/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt b/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt index 1a3e9f18..5548401e 100644 --- a/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt +++ b/app/src/main/java/ani/dantotsu/util/ActivityMarkdownCreator.kt @@ -5,8 +5,8 @@ import android.view.ViewGroup import android.widget.FrameLayout import android.widget.ImageView import android.widget.LinearLayout -import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity +import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.core.widget.addTextChangedListener import ani.dantotsu.R @@ -16,6 +16,7 @@ import ani.dantotsu.databinding.ActivityMarkdownCreatorBinding import ani.dantotsu.initActivity import ani.dantotsu.navBarHeight import ani.dantotsu.openLinkInBrowser +import ani.dantotsu.others.AndroidBug5497Workaround import ani.dantotsu.statusBarHeight import ani.dantotsu.themes.ThemeManager import ani.dantotsu.toast @@ -42,7 +43,7 @@ class ActivityMarkdownCreator : AppCompatActivity() { BOLD("****", 2, R.id.formatBold), ITALIC("**", 1, R.id.formatItalic), STRIKETHROUGH("~~~~", 2, R.id.formatStrikethrough), - SPOILER("||", 2, R.id.formatSpoiler), + SPOILER("~!!~", 2, R.id.formatSpoiler), LINK("[Placeholder](%s)", 0, R.id.formatLink), IMAGE("img(%s)", 0, R.id.formatImage), YOUTUBE("youtube(%s)", 0, R.id.formatYoutube), @@ -68,6 +69,7 @@ class ActivityMarkdownCreator : AppCompatActivity() { bottomMargin += navBarHeight } setContentView(binding.root) + AndroidBug5497Workaround.assistActivity(this) {} val params = binding.createButton.layoutParams as ViewGroup.MarginLayoutParams params.marginEnd = 16 * resources.displayMetrics.density.toInt() @@ -120,12 +122,11 @@ class ActivityMarkdownCreator : AppCompatActivity() { toast(getString(R.string.cannot_be_empty)) return@setOnClickListener } - AlertDialog.Builder(this, R.style.MyPopup).apply { + customAlertDialog().apply { setTitle(R.string.warning) setMessage(R.string.post_to_anilist_warning) - setPositiveButton(R.string.ok) { _, _ -> + setPosButton(R.string.ok) { launchIO { - val isEdit = editId != -1 val success = when (type) { "activity" -> if (isEdit) { @@ -139,7 +140,6 @@ class ActivityMarkdownCreator : AppCompatActivity() { } else { Anilist.mutation.postReply(parentId, text) } - "message" -> if (isEdit) { Anilist.mutation.postMessage(userId, text, editId) } else { @@ -152,11 +152,12 @@ class ActivityMarkdownCreator : AppCompatActivity() { finish() } } - setNeutralButton(R.string.open_rules) { _, _ -> + setNeutralButton(R.string.open_rules) { openLinkInBrowser("https://anilist.co/forum/thread/14") } - setNegativeButton(R.string.cancel, null) - }.show() + setNegButton(R.string.cancel) + show() + } } binding.previewCheckbox.setOnClickListener { @@ -267,9 +268,13 @@ class ActivityMarkdownCreator : AppCompatActivity() { private fun previewMarkdown(preview: Boolean) { val markwon = buildMarkwon(this, false, anilist = true) if (preview) { + binding.editText.isVisible = false binding.editText.isEnabled = false - markwon.setMarkdown(binding.editText, text) + binding.markdownPreview.isVisible = true + markwon.setMarkdown(binding.markdownPreview, AniMarkdown.getBasicAniHTML(text)) } else { + binding.editText.isVisible = true + binding.markdownPreview.isVisible = false binding.editText.setText(text) binding.editText.isEnabled = true val markwonEditor = MarkwonEditor.create(markwon) diff --git a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt index 14e71432..374eb3d1 100644 --- a/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt +++ b/app/src/main/java/ani/dantotsu/util/AlertDialogBuilder.kt @@ -1,5 +1,6 @@ package ani.dantotsu.util +import android.app.Activity import android.app.AlertDialog import android.content.Context import android.view.View @@ -20,7 +21,24 @@ class AlertDialogBuilder(private val context: Context) { private var selectedItemIndex: Int = -1 private var onItemSelected: ((Int) -> Unit)? = null private var customView: View? = null + private var onShow: (() -> Unit)? = null private var attach: ((dialog: AlertDialog) -> Unit)? = null + private var onDismiss: (() -> Unit)? = null + private var onCancel: (() -> Unit)? = null + private var cancelable: Boolean = true + fun setCancelable(cancelable: Boolean): AlertDialogBuilder { + this.cancelable = cancelable + return this + } + fun setOnShowListener(onShow: () -> Unit): AlertDialogBuilder { + this.onShow = onShow + return this + } + fun setOnCancelListener(onCancel: () -> Unit): AlertDialogBuilder { + this.onCancel = onCancel + return this + } + fun setTitle(title: String?): AlertDialogBuilder { this.title = title return this @@ -45,6 +63,10 @@ class AlertDialogBuilder(private val context: Context) { this.customView = view return this } + fun setCustomView(layoutResId: Int): AlertDialogBuilder { + this.customView = View.inflate(context, layoutResId, null) + return this + } fun setPosButton(title: String?, onClick: (() -> Unit)? = null): AlertDialogBuilder { this.posButtonTitle = title @@ -52,11 +74,7 @@ class AlertDialogBuilder(private val context: Context) { return this } - fun setPosButton( - int: Int, - formatArgs: Int? = null, - onClick: (() -> Unit)? = null - ): AlertDialogBuilder { + fun setPosButton(int: Int, formatArgs: Int? = null, onClick: (() -> Unit)? = null): AlertDialogBuilder { this.posButtonTitle = context.getString(int, formatArgs) this.onPositiveButtonClick = onClick return this @@ -68,11 +86,7 @@ class AlertDialogBuilder(private val context: Context) { return this } - fun setNegButton( - int: Int, - formatArgs: Int? = null, - onClick: (() -> Unit)? = null - ): AlertDialogBuilder { + fun setNegButton(int: Int, formatArgs: Int? = null, onClick: (() -> Unit)? = null): AlertDialogBuilder { this.negButtonTitle = context.getString(int, formatArgs) this.onNegativeButtonClick = onClick return this @@ -84,11 +98,7 @@ class AlertDialogBuilder(private val context: Context) { return this } - fun setNeutralButton( - int: Int, - formatArgs: Int? = null, - onClick: (() -> Unit)? = null - ): AlertDialogBuilder { + fun setNeutralButton(int: Int, formatArgs: Int? = null, onClick: (() -> Unit)? = null): AlertDialogBuilder { this.neutralButtonTitle = context.getString(int, formatArgs) this.onNeutralButtonClick = onClick return this @@ -99,22 +109,19 @@ class AlertDialogBuilder(private val context: Context) { return this } - fun singleChoiceItems( - items: Array, - selectedItemIndex: Int = -1, - onItemSelected: (Int) -> Unit - ): AlertDialogBuilder { + fun onDismiss(onDismiss: (() -> Unit)? = null): AlertDialogBuilder { + this.onDismiss = onDismiss + return this + } + + fun singleChoiceItems(items: Array, selectedItemIndex: Int = -1, onItemSelected: (Int) -> Unit): AlertDialogBuilder { this.items = items this.selectedItemIndex = selectedItemIndex this.onItemSelected = onItemSelected return this } - fun multiChoiceItems( - items: Array, - checkedItems: BooleanArray? = null, - onItemsSelected: (BooleanArray) -> Unit - ): AlertDialogBuilder { + fun multiChoiceItems(items: Array, checkedItems: BooleanArray? = null, onItemsSelected: (BooleanArray) -> Unit): AlertDialogBuilder { this.items = items this.checkedItems = checkedItems ?: BooleanArray(items.size) { false } this.onItemsSelected = onItemsSelected @@ -122,15 +129,18 @@ class AlertDialogBuilder(private val context: Context) { } fun show() { + if (context is Activity && context.isFinishing) return // Ensure context is valid + val builder = AlertDialog.Builder(context, R.style.MyPopup) if (title != null) builder.setTitle(title) if (message != null) builder.setMessage(message) if (customView != null) builder.setView(customView) if (items != null) { if (onItemSelected != null) { - builder.setSingleChoiceItems(items, selectedItemIndex) { _, which -> + builder.setSingleChoiceItems(items, selectedItemIndex) { dialog, which -> selectedItemIndex = which onItemSelected?.invoke(which) + dialog.dismiss() } } else if (checkedItems != null && onItemsSelected != null) { builder.setMultiChoiceItems(items, checkedItems) { _, which, isChecked -> @@ -157,15 +167,25 @@ class AlertDialogBuilder(private val context: Context) { dialog.dismiss() } } - builder.setCancelable(false) + if (onCancel != null) { + builder.setOnCancelListener { + onCancel?.invoke() + } + } + builder.setCancelable(cancelable) val dialog = builder.create() attach?.invoke(dialog) + dialog.setOnDismissListener { + onDismiss?.invoke() + } + dialog.setOnShowListener { + onShow?.invoke() + } dialog.window?.setDimAmount(0.8f) dialog.show() } - } fun Context.customAlertDialog(): AlertDialogBuilder { return AlertDialogBuilder(this) -} \ No newline at end of file +} diff --git a/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt b/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt index 3f8c04d7..200b3883 100644 --- a/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt +++ b/app/src/main/java/ani/dantotsu/util/AniMarkdown.kt @@ -1,12 +1,13 @@ package ani.dantotsu.util +import ani.dantotsu.getYoutubeId import ani.dantotsu.util.ColorEditor.Companion.toCssColor class AniMarkdown { //istg anilist has the worst api companion object { - private fun convertNestedImageToHtml(markdown: String): String { + private fun String.convertNestedImageToHtml(): String { val regex = """\[!\[(.*?)]\((.*?)\)]\((.*?)\)""".toRegex() - return regex.replace(markdown) { matchResult -> + return regex.replace(this) { matchResult -> val altText = matchResult.groupValues[1] val imageUrl = matchResult.groupValues[2] val linkUrl = matchResult.groupValues[3] @@ -14,26 +15,49 @@ class AniMarkdown { //istg anilist has the worst api } } - private fun convertImageToHtml(markdown: String): String { + private fun String.convertImageToHtml(): String { val regex = """!\[(.*?)]\((.*?)\)""".toRegex() - return regex.replace(markdown) { matchResult -> + val anilistRegex = """img\(.*?\)""".toRegex() + val markdownImage = regex.replace(this) { matchResult -> val altText = matchResult.groupValues[1] val imageUrl = matchResult.groupValues[2] """$altText""" } + return anilistRegex.replace(markdownImage) { matchResult -> + val imageUrl = matchResult.groupValues[1] + """Image""" + } } - private fun convertLinkToHtml(markdown: String): String { + private fun String.convertLinkToHtml(): String { val regex = """\[(.*?)]\((.*?)\)""".toRegex() - return regex.replace(markdown) { matchResult -> + return regex.replace(this) { matchResult -> val linkText = matchResult.groupValues[1] val linkUrl = matchResult.groupValues[2] """
$linkText""" } } - private fun replaceLeftovers(html: String): String { - return html.replace(" ", " ") + private fun String.convertYoutubeToHtml(): String { + val regex = """
""".toRegex() + return regex.replace(this) { matchResult -> + val url = matchResult.groupValues[1] + val id = getYoutubeId(url) + if (id.isNotEmpty()) { + """
+ $url + + Youtube Link + +
""".trimIndent() + } else { + """Youtube Video""" + } + } + } + + private fun String.replaceLeftovers(): String { + return this.replace(" ", " ") .replace("&", "&") .replace("<", "<") .replace(">", ">") @@ -46,18 +70,29 @@ class AniMarkdown { //istg anilist has the worst api .replace("\n", "
") } - private fun underlineToHtml(html: String): String { - return html.replace("(?s)___(.*?)___".toRegex(), "
$1
") + private fun String.underlineToHtml(): String { + return this.replace("(?s)___(.*?)___".toRegex(), "
$1
") .replace("(?s)__(.*?)__".toRegex(), "
$1
") .replace("(?s)\\s+_([^_]+)_\\s+".toRegex(), "$1") } + private fun String.convertCenterToHtml(): String { + val regex = """~~~(.*?)~~~""".toRegex() + return regex.replace(this) { matchResult -> + val centerText = matchResult.groupValues[1] + """$centerText""" + } + } + fun getBasicAniHTML(html: String): String { - val step0 = convertNestedImageToHtml(html) - val step1 = convertImageToHtml(step0) - val step2 = convertLinkToHtml(step1) - val step3 = replaceLeftovers(step2) - return underlineToHtml(step3) + return html + .convertNestedImageToHtml() + .convertImageToHtml() + .convertLinkToHtml() + .convertYoutubeToHtml() + .convertCenterToHtml() + .replaceLeftovers() + .underlineToHtml() } fun getFullAniHTML(html: String, textColor: Int): String { diff --git a/app/src/main/java/ani/dantotsu/util/AudioHelper.kt b/app/src/main/java/ani/dantotsu/util/AudioHelper.kt new file mode 100644 index 00000000..bfd38be0 --- /dev/null +++ b/app/src/main/java/ani/dantotsu/util/AudioHelper.kt @@ -0,0 +1,58 @@ +package ani.dantotsu.util + +import android.content.Context +import android.media.AudioManager +import android.media.MediaPlayer + +class AudioHelper(private val context: Context) { + + private val audioManager: AudioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager + private var mediaPlayer: MediaPlayer? = null + + fun routeAudioToSpeaker() { + audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN) + audioManager.mode = AudioManager.MODE_IN_COMMUNICATION + audioManager.isSpeakerphoneOn = true + } + + private val maxVolume: Int + get() = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + private var oldVolume: Int = 0 + fun setVolume(percentage: Int) { + oldVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + val volume = (maxVolume * percentage) / 100 + audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0) + } + + fun playAudio(audio: Int) { + mediaPlayer?.release() + mediaPlayer = MediaPlayer.create(context, audio) + mediaPlayer?.setOnCompletionListener { + setVolume(oldVolume) + audioManager.abandonAudioFocus(null) + it.release() + } + mediaPlayer?.setOnPreparedListener { + it.start() + } + } + + fun stopAudio() { + mediaPlayer?.let { + if (it.isPlaying) { + it.stop() + } + it.release() + mediaPlayer = null + } + } + + companion object { + fun run(context: Context, audio: Int) { + val audioHelper = AudioHelper(context) + audioHelper.routeAudioToSpeaker() + audioHelper.setVolume(90) + audioHelper.playAudio(audio) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/util/NumberConverter.kt b/app/src/main/java/ani/dantotsu/util/NumberConverter.kt index 49d41de7..61aefe4e 100644 --- a/app/src/main/java/ani/dantotsu/util/NumberConverter.kt +++ b/app/src/main/java/ani/dantotsu/util/NumberConverter.kt @@ -47,5 +47,9 @@ class NumberConverter { val intBits = java.lang.Float.floatToIntBits(number) return Integer.toBinaryString(intBits) } + + fun Int.ofLength(length: Int): String { + return this.toString().padStart(length, '0') + } } } \ No newline at end of file diff --git a/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt b/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt index 8291c567..ee2a9aa8 100644 --- a/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt +++ b/app/src/main/java/ani/dantotsu/util/StoragePermissions.kt @@ -71,20 +71,17 @@ class StoragePermissions { complete(true) return } - val builder = AlertDialog.Builder(this, R.style.MyPopup) - builder.setTitle(getString(R.string.dir_access)) - builder.setMessage(getString(R.string.dir_access_msg)) - builder.setPositiveButton(getString(R.string.ok)) { dialog, _ -> - launcher.registerForCallback(complete) - launcher.launch() - dialog.dismiss() - } - builder.setNegativeButton(getString(R.string.cancel)) { dialog, _ -> - dialog.dismiss() - complete(false) - } - val dialog = builder.show() - dialog.window?.setDimAmount(0.8f) + customAlertDialog().apply { + setTitle(getString(R.string.dir_access)) + setMessage(getString(R.string.dir_access_msg)) + setPosButton(getString(R.string.ok)) { + launcher.registerForCallback(complete) + launcher.launch() + } + setNegButton(getString(R.string.cancel)) { + complete(false) + } + }.show() } private fun pathToUri(path: String): Uri { diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt index c67eefea..d42ece54 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt @@ -64,10 +64,6 @@ internal class ExtensionGithubApi { val repos = PrefManager.getVal>(PrefName.AnimeExtensionRepos).toMutableList() - if (repos.isEmpty()) { - repos.add("https://raw.githubusercontent.com/aniyomiorg/aniyomi-extensions/repo") - PrefManager.setVal(PrefName.AnimeExtensionRepos, repos.toSet()) - } repos.forEach { try { @@ -95,9 +91,10 @@ internal class ExtensionGithubApi { // Sanity check - a small number of extensions probably means something broke // with the repo generator - if (repoExtensions.size < 10) { - throw Exception() - } + //if (repoExtensions.size < 10) { + // throw Exception() + //} + // No official repo now so this won't be needed anymore. User-made repo can have less than 10 extensions extensions.addAll(repoExtensions) } catch (e: Throwable) { @@ -157,10 +154,6 @@ internal class ExtensionGithubApi { val repos = PrefManager.getVal>(PrefName.MangaExtensionRepos).toMutableList() - if (repos.isEmpty()) { - repos.add("https://raw.githubusercontent.com/keiyoushi/extensions/main") - PrefManager.setVal(PrefName.MangaExtensionRepos, repos.toSet()) - } repos.forEach { try { @@ -188,9 +181,10 @@ internal class ExtensionGithubApi { // Sanity check - a small number of extensions probably means something broke // with the repo generator - if (repoExtensions.size < 10) { - throw Exception() - } + //if (repoExtensions.size < 10) { + // throw Exception() + //} + // No official repo now so this won't be needed anymore. User made repo can have less than 10 extensions. extensions.addAll(repoExtensions) } catch (e: Throwable) { diff --git a/app/src/main/res/drawable/ic_round_history_24.xml b/app/src/main/res/drawable/ic_round_history_24.xml new file mode 100644 index 00000000..1d5164d3 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_history_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_library_books_24.xml b/app/src/main/res/drawable/ic_round_library_books_24.xml new file mode 100644 index 00000000..ee9f6511 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_library_books_24.xml @@ -0,0 +1,11 @@ + + + + diff --git a/app/src/main/res/layout/activity_list.xml b/app/src/main/res/layout/activity_list.xml index 63aa5d81..d70668fc 100644 --- a/app/src/main/res/layout/activity_list.xml +++ b/app/src/main/res/layout/activity_list.xml @@ -55,6 +55,15 @@ app:srcCompat="@drawable/ic_round_search_24" app:tint="?attr/colorOnBackground" /> + + + + diff --git a/app/src/main/res/layout/activity_notification.xml b/app/src/main/res/layout/activity_notification.xml index bfdda606..f4d30071 100644 --- a/app/src/main/res/layout/activity_notification.xml +++ b/app/src/main/res/layout/activity_notification.xml @@ -42,6 +42,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" + android:layout_marginBottom="67dp" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_layout.xml b/app/src/main/res/layout/dialog_layout.xml index 4eae05d3..eab3f479 100644 --- a/app/src/main/res/layout/dialog_layout.xml +++ b/app/src/main/res/layout/dialog_layout.xml @@ -54,7 +54,7 @@ app:cardElevation="0dp"> @@ -235,7 +235,7 @@ app:cardElevation="0dp"> - + tools:listitem="@layout/item_media_source" /> @@ -230,7 +230,7 @@ android:id="@+id/activityLikeContainer" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="12dp" + android:padding="12dp" android:orientation="vertical" tools:ignore="UseCompoundDrawables"> diff --git a/app/src/main/res/layout/item_activity_reply.xml b/app/src/main/res/layout/item_activity_reply.xml index 0b851914..de87b96a 100644 --- a/app/src/main/res/layout/item_activity_reply.xml +++ b/app/src/main/res/layout/item_activity_reply.xml @@ -129,7 +129,6 @@ android:textSize="12sp" tools:visibility="visible" tools:ignore="HardcodedText" /> - diff --git a/app/src/main/res/layout/item_episode_grid.xml b/app/src/main/res/layout/item_episode_grid.xml index d793687d..12dd8636 100644 --- a/app/src/main/res/layout/item_episode_grid.xml +++ b/app/src/main/res/layout/item_episode_grid.xml @@ -13,7 +13,7 @@ app:cardElevation="4dp"> diff --git a/app/src/main/res/layout/item_episode_list.xml b/app/src/main/res/layout/item_episode_list.xml index 3e33c882..a4214537 100644 --- a/app/src/main/res/layout/item_episode_list.xml +++ b/app/src/main/res/layout/item_episode_list.xml @@ -47,7 +47,7 @@ android:indeterminate="true" /> @@ -25,7 +24,7 @@ android:layout_width="40dp" android:layout_height="40dp" android:layout_gravity="center_vertical" - android:layout_marginEnd="3dp" + android:layout_marginEnd="9dp" tools:ignore="ContentDescription" /> + + + + diff --git a/app/src/main/res/layout/item_anime_watch.xml b/app/src/main/res/layout/item_media_source.xml similarity index 93% rename from app/src/main/res/layout/item_anime_watch.xml rename to app/src/main/res/layout/item_media_source.xml index 3169efb0..e4a8d754 100644 --- a/app/src/main/res/layout/item_anime_watch.xml +++ b/app/src/main/res/layout/item_media_source.xml @@ -38,7 +38,7 @@ tools:visibility="visible" /> @@ -318,7 +318,7 @@ + + + + + diff --git a/app/src/main/res/raw/audio.mp3 b/app/src/main/res/raw/audio.mp3 new file mode 100644 index 00000000..49e1e430 Binary files /dev/null and b/app/src/main/res/raw/audio.mp3 differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f3ab9ca1..95c371f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -83,8 +83,7 @@ All - No more notifications - No more activities + Nothing here Followers Write a Message STATUS @@ -156,8 +155,12 @@ Chapter Wrong Title? - Couldn\'t find anything X( \n - Try another source. + Nothing came up from this source.\n + Let\'s look elsewhere. + + + Your downloads are feeling a bit lonely…\n + Try downloading something. %1$s is not supported! Select Server @@ -424,6 +427,8 @@ Use Alarm Manager for reliable Notifications Using Alarm Manger can help fight against battery optimization, but may consume more battery. It also requires the Alarm Manager permission. Use + Remove All Subscriptions + Are you sure you want to remove all subscriptions for %1$s? Notification for Checking Subscriptions Subscriptions Update Frequency : %1$s Subscriptions Update Frequency @@ -640,13 +645,7 @@ This is because it updates every 48 hours automatically (by Anilist). If you really need to update your stats, you can force update your stats after going to this [link](https://anilist.co/settings/lists). How to download Anime? - There are two methods of downloading currently. One is internal and the other is external. If you download internally, then it can be viewed within the app but only the app can open that episode, you cannot move it or share it. The other option is to use external downloading. It requires a download manager to download and a separate video player to watch. External downloads can be shared but you cannot view it within the Dantotsu app.\n\n•To download internally:\n\n1. Tap the download button.\n2. Pick the server and the video quality.\n3. Profit.\n\n•To download externally:\n\n 1. Download 1DM or ADM from Google Play Store. - \n2. Enter the app, give storage access and set your preferences (downloading speed, downloading path etc(optional)) - \n3. Now go to \`Dantotsu > Settings > Common > Download Managers\` and choose the download manager you just set up. - \n4. Go to your desired episode and press on the download icon of any server. There may be a popup to set your preferences again, just press on "Download" and it will be saved in the directed path. - - \n\nNote: Direct downloads are also possible without a manager but it\'s not recommended. -\n\nNerd Note: Internally downloaded episodes are stored in \`Android/data/ani.dantotsu.*/files/Anime_Downloads\`\nYou cannot play those files as they are in \`.exo\` format, split into hundreds of pieces and are encrypted. + There are two methods of downloading. Internal and external. If you download internally, then it can be viewed within the app and tracking will work normally for it. The other option is to use external downloader. It requires a download manager to download and a separate video player to watch. External downloads cannot be viewed within the Dantotsu app.\n\n•To download internally:\n\n1. Tap the download button.\n2. For the first time, it will ask you to set a download location. All your downloads will be stored there.\n3. Pick the server and the video quality.\n4. Profit.\n\n•To download externally:\n\n 1. Download 1DM or ADM from Google Play Store.\n2. Enter the app, give storage access and set your preferences (downloading speed, downloading path etc(optional))\n3. Now go to \`Dantotsu > Settings > Common > Download Managers\` and choose the download manager you just set up.\n4. Go to your desired episode and press on the download icon of any server. There may be a popup to set your preferences again, just press on "Download" and it will be saved in the directed path.\n\nNote: External downloads are also possible without a manager but it\'s not recommended.\n\nNerd Note: Internally downloaded episodes are stored in \`{your set location}/Dantotsu/Anime/*`\nYou can change your download location in settings but your previous downloaded episodes will not show up in the app anymore. How to enable NSFW content? You can enable NSFW content by enabling 18+ contents from this [link](https://anilist.co/settings/media). You also have to enable NSFW extensions in \`Settings > Extensions > NSFW extensions\` @@ -675,6 +674,11 @@ Some useful tips and tricks The following presents some tips and tricks you may or may not know about - \n \n \n - By hold pressing the Dantotsu logo in settings, you can check if there are any new updates manually. \n \n - Hold pressing an error message/tag/synonym or title will copy it. \n \n - You can open an episode with other apps by hold pressing any server for that episode. This helps in streaming the episode using other video players or download the episode using download managers. \n \n - You can set up custom lists using this [link](https://anilist.co/settings/lists). (you need to be signed in) \n \n - If your episode/chapter is not being progressed automatically after you finish watching/reading it, then hold press the status bar(planning/repeating/watching button) of that anime/manga. The next time you start a chapter/finish an episode, you will stumble upon a popup. Press yes there. + I can\'t login to Anilist. + The reason this happens is probably because you\'re not using the default browser.\n\n>You have to set Chrome as your default browser to login to Anilist.\n\n>It takes a few seconds for the login button to display changes.\n\nIf it doesn\'t work then you could possibly be IP banned from Anilist or your ISP banned Anilist themselves. We believe that this is highly unlikely so open [Anilist](https://anilist.co) in your browser to see if that\`s the case. + + What is torrent? How do I use it? + Torrent or formally known as BitTorrent is an internet communication protocol for peer\-to\-peer file sharing. A torrent network doesn\'t use a centralised server to host files. It shares files in a decentralised manner. A torrent network has two types of peers. Seeders & Leachers.\n\n• Seeders : These are peers who completed downloading and has the full file. And now they are sharing this file to other peers which is called seeding. A torrent cannot work without at least one seeder. The more seeder a torrent has, the better.\n\n• Leachers : These are peers who are currently downloading the file. If they currently have 40% downloaded then they will share the 40% to any other peers who requests it.\n\nUnlike a centralised server, torrents have no bandwidth limit. You can get your files from hundreds of seeders at the highest possible speed your internet can offer. But many ISP throttle torrent to slow them down because it\'s demanding on their infrastructure. Use a VPN to circumvent this.\n\n• How to use torrents :\n\n1. Install the Torrent Add-on from \`Dantotsu > Settings > Add-ons > Torrent Add-on.\` \n2. Get a source that provides torrents.\n3. USE A VPN. Using VPN while torrenting only has upsides. Without a VPN your IP address will be exposed, your ISP will throttle your network and you could possibly be fined if you live in a country first world country. DO NOT USE IT WITHOUT A VPN IF YOU DON\'T KNOW WHAT YOU\'RE DOING.\n4. Now use that source to start torrenting. Subscribed! Receiving notifications, when new episodes are released on %1$s. @@ -722,6 +726,7 @@ Installed Manga Color Picker Random Selection + Listed in Library Incognito Mode EXAMPLE Configure @@ -968,6 +973,8 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc Use color from media\'s banner Use your own color for the theme Choose a color + Hide private Media from home screen + Long click "Continue Watching" to access Torrent Add-on Enable Torrent Anime Downloader Add-on @@ -980,6 +987,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc Add-on not found Image + Clear History Failed to install extension due to conflict READING WATCHING @@ -1055,5 +1063,7 @@ Non quae tempore quo provident laudantium qui illo dolor vel quia dolor et exerc Enable Forgot Password (hold clear button for 10 seconds) Hide Notification Dot Private + you have been Ǫ̴̺̙͎̤̫͓̮̰̿͝M̴͇̤͗́̾̈́̑̍̿̈͌͝Ȅ̴̡̨̛͉̣̙̩̲̣̤̟̪̣̎͗̎̆̒̉͆̆̕ͅͅǴ̸̯̬̗̠̙͛͐̀̈͋̀̈̽́̎̿͘͘͝ͅĀ̶̧̲̀ͅ ̴̢̟͕̜̓̾̓C̶̬̜̰̘̝̱̫͓͙̭̈́͐͋̓̏̈̍̓̀̌̾̚Ư̸̛̤̱̈́͆̽͊͛̐̓́̑͘̕̕͝R̸̨̨͈̬̱̺͕̪̪̘͕͎̂͛́̅̆̓̀͝ͅS̴̨̨̛̩̭̗̹̰̭̥͉̮̝̠̓̔͆̂͊͆̀̈́̅̕͘̚͝È̴̢̛̝͈̳͉͈͒͒̒̄̏̈̈́D̸̢̡̨̜̞̩̼̫̹̗̮͛̀̈̋̾̇̕̕͜ͅ + you have been freed diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 72ea4803..46c85c9e 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -9,6 +9,7 @@ false false true + ltr true @font/poppins @style/ShapeAppearanceOverlay.Demo diff --git a/app/src/main/res/xml/anime_preferences.xml b/app/src/main/res/xml/anime_preferences.xml index 57be81d5..a9403ba7 100644 --- a/app/src/main/res/xml/anime_preferences.xml +++ b/app/src/main/res/xml/anime_preferences.xml @@ -1,8 +1,5 @@ - + z