Compare commits

...

96 commits

Author SHA1 Message Date
nomadics9
e009b86153 build: 16 version-0.10.6-0.14.2
Some checks failed
Build / Assemble (push) Has been cancelled
2024-07-26 05:13:37 +03:00
nomadics9
d669dcb618 hotfix 2024-07-26 05:13:08 +03:00
nomadics9
ff5bce074e build: 16 version-0.10.6-0.14.2 2024-07-26 05:02:31 +03:00
nomadics9
d85684317b feat: very simple update checker 2024-07-26 04:58:50 +03:00
nomadics9
3cc52938f2 bugfix: Download season dialog strings 2024-07-26 02:42:48 +03:00
nomadics9
3ff71ae489 build: 15 version-0.10.5-0.14.2 2024-07-21 06:23:54 +03:00
nomadics9
b511b26aa1 feat: Markdown support for disclamer 2024-07-21 06:18:21 +03:00
nomadics9
059b17af9a personal 2024-07-21 05:16:04 +03:00
nomadics9
c293c906d4 build: 15 version-0.10.5-0.14.2 2024-07-21 04:38:32 +03:00
nomadics9
b5d31a6c72 feat: select transcoding codec in network settings / code: clean up, refactors & rework alot of transcoding stuff / bugfixes: mainly deviceId 2024-07-21 04:18:08 +03:00
nomadics9
89d5a332d1 build: 14 bugfixes 2024-07-20 06:42:02 +03:00
nomadics9
4e8ee15d0a bugfixes: getDeviceId() / code: New Enum VideoQuality 2024-07-20 06:31:53 +03:00
nomadics9
36dd8480e1 flow: Version 2024-07-18 23:33:28 +03:00
nomadics9
a6c1ef15b7 hotfix 2024-07-18 07:23:34 +03:00
nomadics9
75ea1dc43a hotfix 2024-07-18 07:11:32 +03:00
nomadics9
2b2e6ce58b build: 12 Embedded subs in downloaded transcode 2024-07-18 06:52:52 +03:00
nomadics9
09427e1de0 feat: Embedded subs in downloaded transcode 2024-07-18 05:20:34 +03:00
nomadics9
fbf4c185f0 build: 11 embedded subs 2024-07-18 04:08:17 +03:00
nomadics9
c84ec082be feat: Embedded subtitle in transcoding stream / bugfixes: Download quality dialog loop / code: clean up 2024-07-18 03:59:38 +03:00
nomadics9
5a8403f6a9 Build: 10 Transcoding Download Merge 2024-07-17 06:30:54 +03:00
nomadics9
362201eddf feat: Transcoding Download / code: Cleanup
Some checks failed
Build / Assemble (push) Has been cancelled
2024-07-17 05:58:26 +03:00
nomadics9
afbf68937d README update 2024-07-14 22:44:52 +03:00
nomadics9
8139119c35 feat: Quality change in player (transcoded stream) 2024-07-14 21:54:21 +03:00
nomadics9
ab090a01d7 feat: Quality change in player 2024-07-14 01:38:34 +03:00
nomadics9
2253780903 fix: hide unused settings 2024-07-08 20:20:15 +03:00
nomadics9
3340ce4e58 refactor: string 2024-07-08 20:09:52 +03:00
nomadics9
ce66bbc0b2 fix: banner on lightmode 2024-07-08 19:52:02 +03:00
nomadics9
e122ef303e fix: auto offline default true 2024-07-08 18:43:45 +03:00
nomadics9
dedb99b73d feat: auto offline 2024-07-08 18:39:04 +03:00
nomadics9
4bf8e6b697 build: Version 7 Built 2024-07-05 20:12:55 +03:00
nomadics9
89fb969d67 build: Version 7 Built 2024-07-05 20:12:37 +03:00
nomadics9
eac3eaa3f3 feat: Rows preferance in libraries view set default to 3 / settings defaults fix 2024-07-05 20:00:21 +03:00
nomadics9
e6c49ea660 flow: fix 2024-07-05 00:01:53 +03:00
nomadics9
44c1b51553 flow: fix 2024-07-04 23:45:13 +03:00
nomadics9
e18e73cca8 flow: fix 2024-07-04 23:24:34 +03:00
nomadics9
5156333a95 flow: Testing 2024-07-04 23:17:54 +03:00
nomadics9
eface29638 build: Version 6 Built 2024-07-04 20:15:48 +03:00
nomadics9
44b6e915ba feat: merged Skip Credits 2024-07-04 20:06:37 +03:00
nomadics9
a1cbea0b92 refactor:package name 2024-07-03 23:40:52 +03:00
nomadics9
e987ac477d feat: Download Season + Download unwatched episodes only dialogue 2024-07-02 17:10:59 +03:00
nomadics9
9baa84e1e7 feat: trickplay on gesture 2024-06-30 22:48:36 +03:00
nomadics9
ed69473e26 feat: trickplay on gesture 2024-06-30 22:48:10 +03:00
nomadics9
73d1b7c099 builds ignore 2024-06-30 21:39:08 +03:00
nomadics9
f269bea184 feat: requests in-app 2024-06-30 21:33:00 +03:00
nomadics9
5c283982bb fix: settings logos 2024-06-30 15:47:50 +03:00
nomadics9
cb4518c86e fix: amoled toggle default true / add: logos for settings without a logo / add: default server / refactor: tv package 2024-06-30 15:11:42 +03:00
nomadics9
2d141b6f0f Fix middle top logo for wide screens 2024-06-30 10:08:13 +03:00
nomadics9
ab19866899
Update README.md 2024-06-29 20:31:03 +03:00
nomadics9
168cfcd19f Add logo on middle topbar and amoled prefrence default true 2024-06-29 19:18:22 +03:00
nomadics9
288c96709e refactor package name 2024-06-29 15:44:54 +03:00
nomadics9
8b5c19b957 refactor app_name 2024-06-29 15:19:49 +03:00
nomadics9
be1da2eb7b gitignore .kotlin 2024-06-29 15:17:03 +03:00
nomadics9
61af530c89 strings 2024-06-29 03:26:18 +03:00
nomadics9
7b508fa456 appCode = 1 2024-06-29 03:19:31 +03:00
nomadics9
fcb58ef8ad personal personlization init 2024-06-29 03:07:12 +03:00
cd16b
09f3d218c1 Remove buttons colors and fix lint 2024-06-25 23:28:54 +02:00
cd16b
350afaa8a9 Fix buttons still visible 2024-06-24 19:09:27 +02:00
cd16b
ba2f9d9708 Update Database 2024-06-24 18:54:10 +02:00
Freya
e74a86da24
Merge branch 'jarnedemeulemeester:main' into auto-offline-mode 2024-06-24 13:32:53 +00:00
cd16b
5ab65062e6 Merge remote-tracking branch 'refs/remotes/origin/main' into Skip-credit
# Conflicts:
#	core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt
#	core/src/main/res/values-it/strings.xml
#	core/src/main/res/values/strings.xml
#	data/schemas/dev.jdtech.jellyfin.database.ServerDatabase/5.json
#	data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt
#	data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryImpl.kt
#	data/src/main/java/dev/jdtech/jellyfin/repository/JellyfinRepositoryOfflineImpl.kt
#	player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt
2024-06-24 12:53:47 +02:00
cd16b
6095c97704 Materia3 buttons and WatchCredits button 2024-06-24 12:01:50 +02:00
cd16b
91cccc55a7 Improve skipButton visibility/usability 2024-06-21 14:36:11 +02:00
cd16b
df984fb24b FindroidSegment 2024-06-20 23:59:24 +02:00
Cd16d
9f3be43eac
Merge branch 'main' into Skip-credit 2024-06-20 10:03:22 +02:00
Cd16d
0999823d6d
Merge branch 'jarnedemeulemeester:main' into Skip-credit 2024-06-02 13:34:01 +02:00
Cd16d
e10ae9c487
Merge branch 'main' into Skip-credit 2024-04-15 20:07:07 +02:00
cd16b
ce9eed6344 fix skipButton hide on click 2024-03-05 16:27:21 +01:00
Cd16d
d4e6351a2d
Merge branch 'main' into Skip-credit 2024-03-04 00:29:24 +01:00
cd16b
f75079f720 fix skipButton still visible after intro end 2024-03-04 00:28:26 +01:00
Cd16d
4a3afe62ef
Update strings-da 2024-02-25 16:45:36 +01:00
Cd16d
50b39d6658
Update strings.xml 2024-02-25 16:36:21 +01:00
Cd16d
3c6e03db89
Merge branch 'main' into Skip-credit 2024-02-25 16:32:47 +01:00
cd16b
05730a513c change text hasNextMediaItem() false 2024-01-23 11:51:45 +01:00
cd16b
674699aeab fix code 2024-01-23 09:58:26 +01:00
cd16b
f9454029f7 clean code 2024-01-22 22:59:04 +01:00
cd16b
2b9831af56 fix next episode no credits 2024-01-22 19:59:01 +01:00
cd16b
6402a6a0c4 fix and change pref_player_intro_skipper_summary 2024-01-22 19:16:44 +01:00
cd16b
a740d3fc71 fix lint 2024-01-22 17:29:44 +01:00
cd16b
9711f4c4fb Close player on the last episode of a series 2024-01-22 17:25:12 +01:00
cd16b
916d71a085 fix PlayerActivityViewModel.kt 2024-01-22 15:40:53 +01:00
cd16b
bdef58d433 Merge remote-tracking branch 'origin/Skip-credit' into Skip-credit 2024-01-22 15:25:16 +01:00
cd16b
4a3a22de37 fix PlayerActivityViewModel.kt 2024-01-22 15:24:49 +01:00
Cd16d
6a917be93f
Merge branch 'jarnedemeulemeester:main' into Skip-credit 2024-01-22 14:20:12 +01:00
cd16b
7f02f3de0a fix lint 2024-01-22 14:14:28 +01:00
cd16b
14eb313b1e fix lint 2024-01-22 13:45:41 +01:00
cd16b
85ff16d843 skip credits 2024-01-22 13:41:40 +01:00
cd16b
92eaefe6e1 skip credits 2024-01-22 13:40:45 +01:00
Freya Winters
216092888a Fix linting issue 2024-01-15 09:02:01 +01:00
Freya
a972832aae
Merge branch 'main' into main 2024-01-11 20:03:49 +00:00
Freya Winters
722267ced2 Merge remote-tracking branch 'upstream/main' 2023-12-28 15:48:58 +01:00
Freya Winters
da64c968bc Fix manual offline mode 2023-10-28 13:38:09 +02:00
Jesper Winters
b5f5a6eaed Fix linting issue 2023-10-27 15:49:20 +02:00
Jesper Winters
49d52f9713 Fix linting issues 2023-10-27 15:37:37 +02:00
Jcuhfehl
5b38bdb1c1 Fix linting issues 2023-10-27 15:37:14 +02:00
Jesper Winters
689c5cff3f Clean up 2023-10-27 15:37:07 +02:00
Jesper Winters
3db0f57437 Add option to turn on offline mode automatically 2023-10-27 15:35:07 +02:00
350 changed files with 6692 additions and 2996 deletions

View file

@ -5,26 +5,27 @@ on:
pull_request:
jobs:
lint:
name: Lint
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v3
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: temurin
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Build with Gradle
run: ./gradlew lintDebug ktlintCheck
# lint:
# name: Lint
# runs-on: ubuntu-22.04
# steps:
# - name: Checkout repository
# uses: actions/checkout@v4
# - name: Validate Gradle Wrapper
# uses: gradle/actions/wrapper-validation@v3
# - name: Set up JDK 17
# uses: actions/setup-java@v4
# with:
# java-version: 17
# distribution: temurin
# - name: Setup Gradle
# uses: gradle/actions/setup-gradle@v3
# - name: Build with Gradle
# run: ./gradlew lintDebug ktlintCheck
assemble:
name: Assemble
runs-on: ubuntu-22.04
if: startsWith(github.event.head_commit.message, 'build:')
steps:
- name: Checkout repository
uses: actions/checkout@v4
@ -38,45 +39,45 @@ jobs:
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Build with Gradle
run: ./gradlew assembleDebug
run: ./gradlew assemble
# Upload all build artifacts in separate steps. This can be shortened once https://github.com/actions/upload-artifact/pull/354 is merged.
- name: Upload artifact phone-libre-arm64-v8a-debug.apk
- name: Upload artifact ananas-v0.10.3-0.14.2-libre-arm64-v8a.apk
uses: actions/upload-artifact@v4
with:
name: phone-libre-arm64-v8a-debug.apk
path: ./app/phone/build/outputs/apk/libre/debug/phone-libre-arm64-v8a-debug.apk
- name: Upload artifact phone-libre-armeabi-v7a-debug.apk
uses: actions/upload-artifact@v4
with:
name: phone-libre-armeabi-v7a-debug.apk
path: ./app/phone/build/outputs/apk/libre/debug/phone-libre-armeabi-v7a-debug.apk
- name: Upload artifact phone-libre-x86_64-debug.apk
uses: actions/upload-artifact@v4
with:
name: phone-libre-x86_64-debug.apk
path: ./app/phone/build/outputs/apk/libre/debug/phone-libre-x86_64-debug.apk
- name: Upload artifact phone-libre-x86-debug.apk
uses: actions/upload-artifact@v4
with:
name: phone-libre-x86-debug.apk
path: ./app/phone/build/outputs/apk/libre/debug/phone-libre-x86-debug.apk
- name: Upload artifact tv-libre-arm64-v8a-debug.apk
uses: actions/upload-artifact@v4
with:
name: tv-libre-arm64-v8a-debug.apk
path: ./app/tv/build/outputs/apk/libre/debug/tv-libre-arm64-v8a-debug.apk
- name: Upload artifact tv-libre-armeabi-v7a-debug.apk
uses: actions/upload-artifact@v4
with:
name: tv-libre-armeabi-v7a-debug.apk
path: ./app/tv/build/outputs/apk/libre/debug/tv-libre-armeabi-v7a-debug.apk
- name: Upload artifact tv-libre-x86_64-debug.apk
uses: actions/upload-artifact@v4
with:
name: tv-libre-x86_64-debug.apk
path: ./app/tv/build/outputs/apk/libre/debug/tv-libre-x86_64-debug.apk
- name: Upload artifact tv-libre-x86-debug.apk
uses: actions/upload-artifact@v4
with:
name: tv-libre-x86-debug.apk
path: ./app/tv/build/outputs/apk/libre/debug/tv-libre-x86-debug.apk
name: phone-libre-arm64-v8a.apk
path: ./app/phone/build/outputs/apk/libre/release/ananas-v0.10.3-0.14.2-libre-arm64-v8a.apk
# - name: Upload artifact phone-libre-armeabi-v7a-debug.apk
# uses: actions/upload-artifact@v4
# with:
# name: phone-libre-armeabi-v7a-debug.apk
# path: ./app/phone/build/outputs/apk/libre/debug/phone-libre-armeabi-v7a-debug.apk
# - name: Upload artifact phone-libre-x86_64-debug.apk
# uses: actions/upload-artifact@v4
# with:
# name: phone-libre-x86_64-debug.apk
# path: ./app/phone/build/outputs/apk/libre/debug/phone-libre-x86_64-debug.apk
# - name: Upload artifact phone-libre-x86-debug.apk
# uses: actions/upload-artifact@v4
# with:
# name: phone-libre-x86-debug.apk
# path: ./app/phone/build/outputs/apk/libre/debug/phone-libre-x86-debug.apk
# - name: Upload artifact tv-libre-arm64-v8a-debug.apk
# uses: actions/upload-artifact@v4
# with:
# name: tv-libre-arm64-v8a-debug.apk
# path: ./app/tv/build/outputs/apk/libre/debug/tv-libre-arm64-v8a-debug.apk
# - name: Upload artifact tv-libre-armeabi-v7a-debug.apk
# uses: actions/upload-artifact@v4
# with:
# name: tv-libre-armeabi-v7a-debug.apk
# path: ./app/tv/build/outputs/apk/libre/debug/tv-libre-armeabi-v7a-debug.apk
# - name: Upload artifact tv-libre-x86_64-debug.apk
# uses: actions/upload-artifact@v4
# with:
# name: tv-libre-x86_64-debug.apk
# path: ./app/tv/build/outputs/apk/libre/debug/tv-libre-x86_64-debug.apk
# - name: Upload artifact tv-libre-x86-debug.apk
# uses: actions/upload-artifact@v4
# with:
# name: tv-libre-x86-debug.apk
# path: ./app/tv/build/outputs/apk/libre/debug/tv-libre-x86-debug.apk

5
.gitignore vendored
View file

@ -10,10 +10,14 @@ local.properties
# Android Studio generated files and folders
captures/
app/phone/libre/release/
.kotlin
.externalNativeBuild/
.cxx/
*.apk
*.dm
output.json
app/phone/libre/release/output-metadata.json
# IntelliJ
*.iml
@ -37,3 +41,4 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
push.sh

View file

@ -1,7 +1,6 @@
This privacy policy pertains the Findroid app.
This privacy policy pertains the Ananas app.
Findroid does not collect or access any personal information. No identifying information or user data of any kind is made available to third-parties.
Ananas does not collect or access any personal information. No identifying information or user data of any kind is made available to third-parties.
This Privacy Policy is effective as of Feb 8th, 2023 and will remain in effect except with respect to any changes in its provisions in the future, which will be in effect immediately after being posted on this page. We reserve the right to update or change our Privacy Policy at any time and you should check this Privacy Policy periodically. Your continued use of the Service after we post any modifications to the Privacy Policy on this page will constitute your acknowledgment of the modifications and your consent to abide and be bound by the modified Privacy Policy.
This Privacy Policy is effective as of Jun 24th, 2024 and will remain in effect except with respect to any changes in its provisions in the future, which will be in effect immediately after being posted on this page. We reserve the right to update or change our Privacy Policy at any time and you should check this Privacy Policy periodically. Your continued use of the Service after we post any modifications to the Privacy Policy on this page will constitute your acknowledgment of the modifications and your consent to abide and be bound by the modified Privacy Policy.
Findroid is published by Jarne Demeulemeester. Inquiries can be submitted to jarnedemeulemeester@gmail.com.

View file

@ -1,21 +1,6 @@
![Findroid banner](images/findroid-banner.png)
# Findroid
![GitHub release (with filter)](https://img.shields.io/github/v/release/jarnedemeulemeester/findroid?style=for-the-badge)
![GitHub repo stars](https://img.shields.io/github/stars/jarnedemeulemeester/findroid?style=for-the-badge)
![GitHub issues](https://img.shields.io/github/issues/jarnedemeulemeester/findroid?style=for-the-badge)
![GitHub pull requests](https://img.shields.io/github/issues-pr/jarnedemeulemeester/findroid?style=for-the-badge)
![GitHub all releases](https://img.shields.io/github/downloads/jarnedemeulemeester/findroid/total?style=for-the-badge)
![GitHub](https://img.shields.io/github/license/jarnedemeulemeester/findroid?style=for-the-badge)
Findroid is third-party Android application for Jellyfin that provides a native user interface to browse and play movies and series.
I am developing this application in my spare time.
**This project is in its early stages so expect bugs.**
<a href='https://play.google.com/store/apps/details?id=dev.jdtech.jellyfin'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png' height="80"/></a><a href='http://www.amazon.com/gp/product/B0BTWC8DNZ'><img alt='Available at Amazon Appstore' src='https://user-images.githubusercontent.com/32322857/219019331-027a6775-7362-44bb-a026-281f71e9b37b.png' height="80"/></a><a href='https://apt.izzysoft.de/fdroid/index/apk/dev.jdtech.jellyfin'><img alt='Get it on IzzyOnDroid' src='https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png' height="80"/></a>
# Ananas
Personal fork
## Screenshots
| Home | Library | Movie | Season | Episode |
|-------------------------------------|-------------------------------------|---------------------------------|-----------------------------------|-------------------------------------|
@ -24,8 +9,9 @@ I am developing this application in my spare time.
## Features
- Completely native interface
- Supported media items: movies, series, seasons, episodes
- Direct play only, (no transcoding)
- Offline playback / downloads
- Direct play and Transcoding
- Offline playback / downloads
- Transcoding Downloads (Original - 720p - 480p - 360p)
- ExoPlayer
- Video codecs: H.263, H.264, H.265, VP8, VP9, AV1
- Support depends on Android device
@ -49,20 +35,8 @@ I am developing this application in my spare time.
- Websocket connection (Syncplay)
- Chromecast support
## Translating
[JDTech Weblate](https://weblate.jdtech.dev) is a selfhosted instance of Weblate where you can translate this project and future projects of mine.
## Questions?
[![](https://dcbadge.vercel.app/api/server/tg5VvTFwTV)](https://discord.gg/tg5VvTFwTV)\
We have a Discord server to discuss future development or ask general questions.
## License
This project is licensed under [GPLv3](LICENSE).
The logo is a combination of the Jellyfin logo and the Android robot.
The Android robot is reproduced or modified from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.
Android is a trademark of Google LLC.
Google Play and the Google Play logo are trademarks of Google LLC.

View file

@ -0,0 +1,87 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.nomadics9.ananas",
"variantName": "AnanasRelease",
"elements": [
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 16,
"versionName": "0.10.6-0.14.2",
"outputFile": "ananas-v0.10.6-0.14.2-Ananas-armeabi-v7a.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "arm64-v8a"
}
],
"attributes": [],
"versionCode": 16,
"versionName": "0.10.6-0.14.2",
"outputFile": "ananas-v0.10.6-0.14.2-Ananas-arm64-v8a.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 16,
"versionName": "0.10.6-0.14.2",
"outputFile": "ananas-v0.10.6-0.14.2-Ananas-x86_64.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86"
}
],
"attributes": [],
"versionCode": 16,
"versionName": "0.10.6-0.14.2",
"outputFile": "ananas-v0.10.6-0.14.2-Ananas-x86.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/ananas-v0.10.6-0.14.2-Ananas-armeabi-v7a.dm",
"baselineProfiles/1/ananas-v0.10.6-0.14.2-Ananas-arm64-v8a.dm",
"baselineProfiles/1/ananas-v0.10.6-0.14.2-Ananas-x86_64.dm",
"baselineProfiles/1/ananas-v0.10.6-0.14.2-Ananas-x86.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/ananas-v0.10.6-0.14.2-Ananas-armeabi-v7a.dm",
"baselineProfiles/0/ananas-v0.10.6-0.14.2-Ananas-arm64-v8a.dm",
"baselineProfiles/0/ananas-v0.10.6-0.14.2-Ananas-x86_64.dm",
"baselineProfiles/0/ananas-v0.10.6-0.14.2-Ananas-x86.dm"
]
}
],
"minSdkVersionForDexing": 28
}

View file

@ -10,19 +10,23 @@ plugins {
}
android {
namespace = "dev.jdtech.jellyfin"
namespace = "com.nomadics9.ananas"
compileSdk = Versions.compileSdk
buildToolsVersion = Versions.buildTools
defaultConfig {
applicationId = "dev.jdtech.jellyfin"
applicationId = "com.nomadics9.ananas"
minSdk = Versions.minSdk
targetSdk = Versions.targetSdk
versionCode = Versions.appCode
versionName = Versions.appName
testInstrumentationRunner = "dev.jdtech.jellyfin.HiltTestRunner"
testInstrumentationRunner = "com.nomadics9.ananas.HiltTestRunner"
buildConfigField( "String", "DEFAULT_SERVER_ADDRESS", "\" \"")
buildConfigField( "String", "REQUEST_SERVER_ADDRESS", "\" \"")
buildConfigField("String", "FORGET_PASSWORD_ADDRESS", "\" \"")
buildConfigField("String", "UPDATE_ADDRESS", "\" \"")
}
applicationVariants.all {
@ -31,7 +35,7 @@ android {
.map { it as com.android.build.gradle.internal.api.BaseVariantOutputImpl }
.forEach { output ->
if (variant.buildType.name == "release") {
val outputFileName = "findroid-v${variant.versionName}-${variant.flavorName}-${output.getFilter("ABI")}.apk"
val outputFileName = "ananas-v${variant.versionName}-${variant.flavorName}-${output.getFilter("ABI")}.apk"
output.outputFileName = outputFileName
}
}
@ -57,10 +61,18 @@ android {
flavorDimensions += "variant"
productFlavors {
register("libre") {
create("libre") {
dimension = "variant"
isDefault = true
}
create("Ananas") {
dimension = "variant"
isDefault = false
buildConfigField( "String", "DEFAULT_SERVER_ADDRESS", "\"https://askar.tv\"")
buildConfigField( "String", "REQUEST_SERVER_ADDRESS", "\"https://r.askar.tv\"")
buildConfigField("String", "FORGET_PASSWORD_ADDRESS", "\"https://user.askar.tv/my/account\"")
buildConfigField("String", "UPDATE_ADDRESS", "\"https://fs.nmd.mov/p/ananas.apk\"")
}
}
splits {
@ -122,6 +134,7 @@ dependencies {
implementation(libs.material)
implementation(libs.media3.ffmpeg.decoder)
implementation(libs.timber)
implementation(libs.markwon)
coreLibraryDesugaring(libs.android.desugar.jdk)

View file

@ -0,0 +1,87 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.nomadics9.ananas",
"variantName": "libreRelease",
"elements": [
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 11,
"versionName": "0.10.1-0.14.2",
"outputFile": "ananas-v0.10.1-0.14.2-libre-armeabi-v7a.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 11,
"versionName": "0.10.1-0.14.2",
"outputFile": "ananas-v0.10.1-0.14.2-libre-x86_64.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "arm64-v8a"
}
],
"attributes": [],
"versionCode": 11,
"versionName": "0.10.1-0.14.2",
"outputFile": "ananas-v0.10.1-0.14.2-libre-arm64-v8a.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86"
}
],
"attributes": [],
"versionCode": 11,
"versionName": "0.10.1-0.14.2",
"outputFile": "ananas-v0.10.1-0.14.2-libre-x86.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/ananas-v0.10.1-0.14.2-libre-armeabi-v7a.dm",
"baselineProfiles/1/ananas-v0.10.1-0.14.2-libre-x86_64.dm",
"baselineProfiles/1/ananas-v0.10.1-0.14.2-libre-arm64-v8a.dm",
"baselineProfiles/1/ananas-v0.10.1-0.14.2-libre-x86.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/ananas-v0.10.1-0.14.2-libre-armeabi-v7a.dm",
"baselineProfiles/0/ananas-v0.10.1-0.14.2-libre-x86_64.dm",
"baselineProfiles/0/ananas-v0.10.1-0.14.2-libre-arm64-v8a.dm",
"baselineProfiles/0/ananas-v0.10.1-0.14.2-libre-x86.dm"
]
}
],
"minSdkVersionForDexing": 28
}

View file

@ -20,16 +20,16 @@
# hide the original source file name.
#-renamesourcefileattribute SourceFile
-keepnames class dev.jdtech.jellyfin.models.PlayerItem
-keepnames class com.nomadics9.ananas.models.PlayerItem
# ProGuard thinks all SettingsFragments are unused
-keep class dev.jdtech.jellyfin.fragments.SettingsLanguageFragment
-keep class dev.jdtech.jellyfin.fragments.SettingsAppearanceFragment
-keep class dev.jdtech.jellyfin.fragments.SettingsDownloadsFragment
-keep class dev.jdtech.jellyfin.fragments.SettingsPlayerFragment
-keep class dev.jdtech.jellyfin.fragments.SettingsDeviceFragment
-keep class dev.jdtech.jellyfin.fragments.SettingsCacheFragment
-keep class dev.jdtech.jellyfin.fragments.SettingsNetworkFragment
-keep class com.nomadics9.ananas.fragments.SettingsLanguageFragment
-keep class com.nomadics9.ananas.fragments.SettingsAppearanceFragment
-keep class com.nomadics9.ananas.fragments.SettingsDownloadsFragment
-keep class com.nomadics9.ananas.fragments.SettingsPlayerFragment
-keep class com.nomadics9.ananas.fragments.SettingsDeviceFragment
-keep class com.nomadics9.ananas.fragments.SettingsCacheFragment
-keep class com.nomadics9.ananas.fragments.SettingsNetworkFragment
# These classes are from okhttp and are not used in Android
-dontwarn org.bouncycastle.jsse.BCSSLSocket

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.app.Application
import android.content.Context

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.util.Log
import androidx.hilt.work.HiltWorkerFactory
@ -19,7 +19,7 @@ import androidx.work.testing.WorkManagerTestInitHelper
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.UninstallModules
import dev.jdtech.jellyfin.di.DatabaseModule
import com.nomadics9.ananas.di.DatabaseModule
import org.hamcrest.CoreMatchers.allOf
import org.hamcrest.CoreMatchers.not
import org.junit.Before
@ -76,9 +76,9 @@ class MainActivityTest {
waitForElement(allOf(withText("Movies"), isDisplayed()))
onView(withText("Movies")).perform(click())
// Navigate to The Boy in the Plastic Bubble
waitForElement(allOf(withText("The Boy in the Plastic Bubble"), isDisplayed()))
onView(withText("The Boy in the Plastic Bubble")).perform(click())
// Navigate to Battle of the Stars
waitForElement(allOf(withText("Battle of the Stars"), isDisplayed()))
onView(withText("Battle of the Stars")).perform(click())
// Play the movie
waitForElement(allOf(withId(R.id.play_button), isEnabled()))

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.view.View
import android.view.ViewTreeObserver

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.di
package com.nomadics9.ananas.di
import android.content.Context
import androidx.room.Room
@ -7,8 +7,8 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dev.jdtech.jellyfin.database.ServerDatabase
import dev.jdtech.jellyfin.database.ServerDatabaseDao
import com.nomadics9.ananas.database.ServerDatabase
import com.nomadics9.ananas.database.ServerDatabaseDao
import javax.inject.Singleton
@Module

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
@ -14,7 +14,7 @@ import com.google.android.material.color.DynamicColorsOptions
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@HiltAndroidApp
class BaseApplication : Application(), Configuration.Provider, ImageLoaderFactory {

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.os.Bundle
import android.view.View
@ -9,7 +9,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.updatePadding
import androidx.media3.session.MediaSession
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import com.nomadics9.ananas.viewmodels.PlayerActivityViewModel
abstract class BasePlayerActivity : AppCompatActivity() {

View file

@ -1,20 +1,20 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.view.View
import android.widget.ImageView
import androidx.annotation.DrawableRes
import coil.load
import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.User
import com.nomadics9.ananas.api.JellyfinApi
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.User
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.BaseItemPerson
import org.jellyfin.sdk.model.api.ImageType
import java.util.UUID
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
fun bindItemImage(imageView: ImageView, item: BaseItemDto) {
val itemId =

View file

@ -1,12 +1,10 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.os.Bundle
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updatePadding
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.NavGraph
import androidx.navigation.fragment.NavHostFragment
@ -21,12 +19,16 @@ import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.google.android.material.navigation.NavigationBarView
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.databinding.ActivityMainBinding
import dev.jdtech.jellyfin.viewmodels.MainViewModel
import dev.jdtech.jellyfin.work.SyncWorker
import com.nomadics9.ananas.database.ServerDatabaseDao
import com.nomadics9.ananas.databinding.ActivityMainBinding
import com.nomadics9.ananas.viewmodels.MainViewModel
import com.nomadics9.ananas.work.SyncWorker
import com.nomadics9.ananas.repository.JellyfinRepository
import com.nomadics9.ananas.utils.restart
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@ -38,6 +40,9 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var database: ServerDatabaseDao
@Inject
lateinit var jellyfinRepository: JellyfinRepository
@Inject
lateinit var appPreferences: AppPreferences
@ -48,21 +53,6 @@ class MainActivity : AppCompatActivity() {
scheduleUserDataSync()
applyTheme()
setupActivity()
// Temp fix insets because SDK 35 enables edge to edge by default. This will probably be removed once we move to compose
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val bars = insets.getInsets(
WindowInsetsCompat.Type.systemBars()
or WindowInsetsCompat.Type.displayCutout(),
)
v.updatePadding(
left = bars.left,
top = bars.top,
right = bars.right,
bottom = bars.bottom,
)
WindowInsetsCompat.CONSUMED
}
}
@OptIn(NavigationUiSaveStateControl::class)
@ -86,10 +76,18 @@ class MainActivity : AppCompatActivity() {
val navView: NavigationBarView = binding.navView as NavigationBarView
if (appPreferences.offlineMode) {
appPreferences.isOffline = true
}
if (appPreferences.isOffline) {
navView.menu.clear()
navView.inflateMenu(CoreR.menu.bottom_nav_menu_offline)
}
if (!appPreferences.isOffline && appPreferences.autoOffline) {
testServerConnection()
}
setSupportActionBar(binding.mainToolbar)
// Passing each menu ID as a set of Ids because each
@ -110,7 +108,7 @@ class MainActivity : AppCompatActivity() {
navController.addOnDestinationChangedListener { _, destination, _ ->
binding.navView.visibility = when (destination.id) {
R.id.twoPaneSettingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, com.mikepenz.aboutlibraries.R.id.about_libraries_dest, R.id.usersFragment, R.id.serverAddressesFragment -> View.GONE
R.id.twoPaneSettingsFragment, R.id.serverSelectFragment, R.id.addServerFragment, R.id.loginFragment, com.mikepenz.aboutlibraries.R.id.about_libraries_dest, R.id.usersFragment, R.id.serverAddressesFragment, R.id.requestsWebFragment -> View.GONE
else -> View.VISIBLE
}
if (destination.id == com.mikepenz.aboutlibraries.R.id.about_libraries_dest) {
@ -170,4 +168,18 @@ class MainActivity : AppCompatActivity() {
setTheme(CoreR.style.ThemeOverlay_Findroid_Amoled)
}
}
private fun testServerConnection() {
val activity = this
lifecycleScope.launch {
try {
jellyfinRepository.getPublicSystemInfo()
// Give the UI a chance to load
delay(100)
} catch (e: Exception) {
appPreferences.isOffline = true
activity.restart()
}
}
}
}

View file

@ -1,6 +1,5 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.annotation.SuppressLint
import android.app.AppOpsManager
import android.app.PictureInPictureParams
import android.content.Context
@ -35,25 +34,25 @@ import androidx.media3.ui.PlayerControlView
import androidx.media3.ui.PlayerView
import androidx.navigation.navArgs
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nomadics9.ananas.databinding.ActivityPlayerBinding
import com.nomadics9.ananas.dialogs.SpeedSelectionDialogFragment
import com.nomadics9.ananas.dialogs.TrackSelectionDialogFragment
import com.nomadics9.ananas.models.FindroidSegment
import com.nomadics9.ananas.utils.PlayerGestureHelper
import com.nomadics9.ananas.utils.PreviewScrubListener
import com.nomadics9.ananas.viewmodels.PlayerActivityViewModel
import com.nomadics9.ananas.viewmodels.PlayerEvents
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.databinding.ActivityPlayerBinding
import dev.jdtech.jellyfin.dialogs.SpeedSelectionDialogFragment
import dev.jdtech.jellyfin.dialogs.TrackSelectionDialogFragment
import dev.jdtech.jellyfin.models.VideoQuality
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
import dev.jdtech.jellyfin.utils.PreviewScrubListener
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerEvents
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
import com.nomadics9.ananas.models.VideoQuality
var isControlsLocked: Boolean = false
@AndroidEntryPoint
class PlayerActivity : BasePlayerActivity() {
@Inject
lateinit var appPreferences: AppPreferences
@ -62,6 +61,8 @@ class PlayerActivity : BasePlayerActivity() {
override val viewModel: PlayerActivityViewModel by viewModels()
private var previewScrubListener: PreviewScrubListener? = null
private var wasZoom: Boolean = false
private var oldSegment: FindroidSegment? = null
private var buttonPressed: Boolean = false
private val isPipSupported by lazy {
// Check if device has PiP feature
@ -110,12 +111,13 @@ class PlayerActivity : BasePlayerActivity() {
configureInsets(lockedControls)
if (appPreferences.playerGestures) {
playerGestureHelper = PlayerGestureHelper(
appPreferences,
this,
binding.playerView,
getSystemService(Context.AUDIO_SERVICE) as AudioManager,
)
playerGestureHelper =
PlayerGestureHelper(
appPreferences,
this,
binding.playerView,
getSystemService(AUDIO_SERVICE) as AudioManager,
)
}
binding.playerView.findViewById<View>(R.id.back_button).setOnClickListener {
@ -127,7 +129,8 @@ class PlayerActivity : BasePlayerActivity() {
val audioButton = binding.playerView.findViewById<ImageButton>(R.id.btn_audio_track)
val subtitleButton = binding.playerView.findViewById<ImageButton>(R.id.btn_subtitle)
val speedButton = binding.playerView.findViewById<ImageButton>(R.id.btn_speed)
val skipIntroButton = binding.playerView.findViewById<Button>(R.id.btn_skip_intro)
val skipButton = binding.playerView.findViewById<Button>(R.id.btn_skip_intro)
val watchCreditsButton = binding.playerView.findViewById<Button>(R.id.btn_watch_credits)
val pipButton = binding.playerView.findViewById<ImageButton>(R.id.btn_pip)
val lockButton = binding.playerView.findViewById<ImageButton>(R.id.btn_lockview)
val unlockButton = binding.playerView.findViewById<ImageButton>(R.id.btn_unlock)
@ -141,19 +144,101 @@ class PlayerActivity : BasePlayerActivity() {
// Title
videoNameTextView.text = currentItemTitle
// Skip Intro button
skipIntroButton.isVisible = !isInPictureInPictureMode && currentIntro != null
skipIntroButton.setOnClickListener {
currentIntro?.let {
binding.playerView.player?.seekTo((it.introEnd * 1000).toLong())
// Skip Button
if (currentSegment != oldSegment) buttonPressed = false
// Button Visibility and Text
when (currentSegment?.type) {
"intro" -> {
skipButton.text =
getString(CoreR.string.skip_intro_button)
skipButton.isVisible =
!isInPictureInPictureMode &&
!buttonPressed &&
(
showSkip == true ||
(binding.playerView.isControllerFullyVisible && currentSegment?.skip == true)
)
watchCreditsButton.isVisible = false
}
"credit" -> {
skipButton.text =
if (binding.playerView.player?.hasNextMediaItem() == true) {
getString(CoreR.string.skip_credit_button)
} else {
getString(CoreR.string.skip_credit_button_last)
}
skipButton.isVisible =
!isInPictureInPictureMode &&
!buttonPressed &&
currentSegment?.skip == true &&
!binding.playerView.isControllerFullyVisible
watchCreditsButton.isVisible = skipButton.isVisible
}
else -> {
skipButton.isVisible = false
watchCreditsButton.isVisible = false
}
}
binding.playerView.setControllerVisibilityListener(
PlayerView.ControllerVisibilityListener { visibility ->
when (currentSegment?.type) {
"intro" -> {
skipButton.isVisible =
!buttonPressed &&
(showSkip == true || (visibility == View.VISIBLE && currentSegment?.skip == true))
}
"credit" -> {
skipButton.isVisible =
!buttonPressed &&
currentSegment?.skip == true &&
visibility == View.GONE
watchCreditsButton.isVisible = skipButton.isVisible
}
}
},
)
// onClick
if (currentSegment?.type == "credit") {
watchCreditsButton.setOnClickListener {
buttonPressed = true
skipButton.isVisible = false
watchCreditsButton.isVisible = false
}
}
skipButton.setOnClickListener {
when (currentSegment?.type) {
"intro" -> {
currentSegment?.let {
binding.playerView.player?.seekTo((it.endTime * 1000).toLong())
}
}
"credit" -> {
if (binding.playerView.player?.hasNextMediaItem() == true) {
binding.playerView.player?.seekToNext()
} else {
finish()
}
}
}
buttonPressed = true
skipButton.isVisible = false
watchCreditsButton.isVisible = false
}
oldSegment = currentSegment
// Trickplay
previewScrubListener?.let {
it.currentTrickplay = currentTrickplay
}
playerGestureHelper?.let {
it.currentTrickplay = currentTrickplay
}
// Chapters
if (appPreferences.showChapterMarkers && currentChapters != null) {
currentChapters?.let { chapters ->
@ -191,7 +276,8 @@ class PlayerActivity : BasePlayerActivity() {
if (appPreferences.playerPipGesture) {
try {
setPictureInPictureParams(pipParams(event.isPlaying))
} catch (_: IllegalArgumentException) { }
} catch (_: IllegalArgumentException) {
}
}
}
}
@ -290,7 +376,6 @@ class PlayerActivity : BasePlayerActivity() {
viewModel.initializePlayer(args.items)
}
@SuppressLint("MissingSuperCall")
override fun onUserLeaveHint() {
super.onUserLeaveHint()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S &&
@ -305,34 +390,38 @@ class PlayerActivity : BasePlayerActivity() {
private fun pipParams(enableAutoEnter: Boolean = viewModel.player.isPlaying): PictureInPictureParams {
val displayAspectRatio = Rational(binding.playerView.width, binding.playerView.height)
val aspectRatio = binding.playerView.player?.videoSize?.let {
Rational(
it.width.coerceAtMost((it.height * 2.39f).toInt()),
it.height.coerceAtMost((it.width * 2.39f).toInt()),
)
}
val aspectRatio =
binding.playerView.player?.videoSize?.let {
Rational(
it.width.coerceAtMost((it.height * 2.39f).toInt()),
it.height.coerceAtMost((it.width * 2.39f).toInt()),
)
}
val sourceRectHint = if (displayAspectRatio < aspectRatio!!) {
val space = ((binding.playerView.height - (binding.playerView.width.toFloat() / aspectRatio.toFloat())) / 2).toInt()
Rect(
0,
space,
binding.playerView.width,
(binding.playerView.width.toFloat() / aspectRatio.toFloat()).toInt() + space,
)
} else {
val space = ((binding.playerView.width - (binding.playerView.height.toFloat() * aspectRatio.toFloat())) / 2).toInt()
Rect(
space,
0,
(binding.playerView.height.toFloat() * aspectRatio.toFloat()).toInt() + space,
binding.playerView.height,
)
}
val sourceRectHint =
if (displayAspectRatio < aspectRatio!!) {
val space = ((binding.playerView.height - (binding.playerView.width.toFloat() / aspectRatio.toFloat())) / 2).toInt()
Rect(
0,
space,
binding.playerView.width,
(binding.playerView.width.toFloat() / aspectRatio.toFloat()).toInt() + space,
)
} else {
val space = ((binding.playerView.width - (binding.playerView.height.toFloat() * aspectRatio.toFloat())) / 2).toInt()
Rect(
space,
0,
(binding.playerView.height.toFloat() * aspectRatio.toFloat()).toInt() + space,
binding.playerView.height,
)
}
val builder = PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.setSourceRectHint(sourceRectHint)
val builder =
PictureInPictureParams
.Builder()
.setAspectRatio(aspectRatio)
.setSourceRectHint(sourceRectHint)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setAutoEnterEnabled(enableAutoEnter)
@ -390,25 +479,29 @@ class PlayerActivity : BasePlayerActivity() {
playerGestureHelper?.updateZoomMode(false)
// Brightness mode Auto
window.attributes = window.attributes.apply {
screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
}
window.attributes =
window.attributes.apply {
screenBrightness = WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE
}
}
false -> {
binding.playerView.useController = true
playerGestureHelper?.updateZoomMode(wasZoom)
// Override auto brightness
window.attributes = window.attributes.apply {
screenBrightness = if (appPreferences.playerBrightnessRemember) {
appPreferences.playerBrightness
} else {
Settings.System.getInt(
contentResolver,
Settings.System.SCREEN_BRIGHTNESS,
).toFloat() / 255
window.attributes =
window.attributes.apply {
screenBrightness =
if (appPreferences.playerBrightnessRemember) {
appPreferences.playerBrightness
} else {
Settings.System
.getInt(
contentResolver,
Settings.System.SCREEN_BRIGHTNESS,
).toFloat() / 255
}
}
}
}
}
}

View file

@ -1,13 +1,13 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.bindCardItemImage
import dev.jdtech.jellyfin.databinding.CollectionItemBinding
import dev.jdtech.jellyfin.models.FindroidCollection
import com.nomadics9.ananas.bindCardItemImage
import com.nomadics9.ananas.databinding.CollectionItemBinding
import com.nomadics9.ananas.models.FindroidCollection
class CollectionListAdapter(
private val onClickListener: (collection: FindroidCollection) -> Unit,
@ -47,4 +47,4 @@ class CollectionListAdapter(
}
holder.bind(collection)
}
}
}

View file

@ -1,12 +1,12 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.DiscoveredServerItemBinding
import dev.jdtech.jellyfin.models.DiscoveredServer
import com.nomadics9.ananas.databinding.DiscoveredServerItemBinding
import com.nomadics9.ananas.models.DiscoveredServer
class DiscoveredServerListAdapter(
private val clickListener: (server: DiscoveredServer) -> Unit,
@ -55,4 +55,4 @@ class DiscoveredServerListAdapter(
holder.itemView.setOnClickListener { clickListener(server) }
holder.bind(server)
}
}
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.text.Html.fromHtml
import android.util.TypedValue
@ -9,15 +9,15 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.bindCardItemImage
import dev.jdtech.jellyfin.bindItemBackdropById
import dev.jdtech.jellyfin.bindSeasonPoster
import dev.jdtech.jellyfin.databinding.EpisodeItemBinding
import dev.jdtech.jellyfin.databinding.SeasonHeaderBinding
import dev.jdtech.jellyfin.models.EpisodeItem
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.bindCardItemImage
import com.nomadics9.ananas.bindItemBackdropById
import com.nomadics9.ananas.bindSeasonPoster
import com.nomadics9.ananas.databinding.EpisodeItemBinding
import com.nomadics9.ananas.databinding.SeasonHeaderBinding
import com.nomadics9.ananas.models.EpisodeItem
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.isDownloaded
import com.nomadics9.ananas.core.R as CoreR
private const val ITEM_VIEW_TYPE_HEADER = 0
private const val ITEM_VIEW_TYPE_EPISODE = 1
@ -123,4 +123,4 @@ class EpisodeListAdapter(
is EpisodeItem.Episode -> ITEM_VIEW_TYPE_EPISODE
}
}
}
}

View file

@ -1,14 +1,14 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.Constants
import dev.jdtech.jellyfin.databinding.FavoriteSectionBinding
import dev.jdtech.jellyfin.models.FavoriteSection
import dev.jdtech.jellyfin.models.FindroidItem
import com.nomadics9.ananas.Constants
import com.nomadics9.ananas.databinding.FavoriteSectionBinding
import com.nomadics9.ananas.models.FavoriteSection
import com.nomadics9.ananas.models.FindroidItem
class FavoritesListAdapter(
private val onItemClickListener: (item: FindroidItem) -> Unit,
@ -59,4 +59,4 @@ class FavoritesListAdapter(
val collection = getItem(position)
holder.bind(collection, onItemClickListener)
}
}
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.util.TypedValue
import android.view.LayoutInflater
@ -8,13 +8,13 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.bindCardItemImage
import dev.jdtech.jellyfin.databinding.HomeEpisodeItemBinding
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.bindCardItemImage
import com.nomadics9.ananas.databinding.HomeEpisodeItemBinding
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.isDownloaded
import com.nomadics9.ananas.core.R as CoreR
class HomeEpisodeListAdapter(private val onClickListener: (item: FindroidItem) -> Unit) : ListAdapter<FindroidItem, HomeEpisodeListAdapter.EpisodeViewHolder>(DiffCallback) {
class EpisodeViewHolder(
@ -81,4 +81,4 @@ class HomeEpisodeListAdapter(private val onClickListener: (item: FindroidItem) -
}
holder.bind(item)
}
}
}

View file

@ -1,12 +1,12 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.bindPersonImage
import dev.jdtech.jellyfin.databinding.PersonItemBinding
import com.nomadics9.ananas.bindPersonImage
import com.nomadics9.ananas.databinding.PersonItemBinding
import org.jellyfin.sdk.model.api.BaseItemPerson
class PersonListAdapter(private val clickListener: (item: BaseItemPerson) -> Unit) : ListAdapter<BaseItemPerson, PersonListAdapter.PersonViewHolder>(DiffCallback) {
@ -45,4 +45,4 @@ class PersonListAdapter(private val clickListener: (item: BaseItemPerson) -> Uni
holder.bind(item)
holder.itemView.setOnClickListener { clickListener(item) }
}
}
}

View file

@ -1,12 +1,12 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.ServerAddressListItemBinding
import dev.jdtech.jellyfin.models.ServerAddress
import com.nomadics9.ananas.databinding.ServerAddressListItemBinding
import com.nomadics9.ananas.models.ServerAddress
class ServerAddressAdapter(
private val clickListener: (address: ServerAddress) -> Unit,
@ -48,4 +48,4 @@ class ServerAddressAdapter(
holder.itemView.setOnLongClickListener { longClickListener(address) }
holder.bind(address)
}
}
}

View file

@ -1,12 +1,12 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.ServerItemBinding
import dev.jdtech.jellyfin.models.Server
import com.nomadics9.ananas.databinding.ServerItemBinding
import com.nomadics9.ananas.models.Server
class ServerGridAdapter(
private val onClickListener: (server: Server) -> Unit,
@ -46,4 +46,4 @@ class ServerGridAdapter(
}
holder.bind(server)
}
}
}

View file

@ -1,13 +1,13 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.bindUserImage
import dev.jdtech.jellyfin.databinding.UserListItemBinding
import dev.jdtech.jellyfin.models.User
import com.nomadics9.ananas.bindUserImage
import com.nomadics9.ananas.databinding.UserListItemBinding
import com.nomadics9.ananas.models.User
class UserListAdapter(
private val clickListener: (user: User) -> Unit,
@ -50,4 +50,4 @@ class UserListAdapter(
holder.itemView.setOnLongClickListener { longClickListener(user) }
holder.bind(user)
}
}
}

View file

@ -1,13 +1,13 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.bindUserImage
import dev.jdtech.jellyfin.databinding.UserItemBinding
import dev.jdtech.jellyfin.models.User
import com.nomadics9.ananas.bindUserImage
import com.nomadics9.ananas.databinding.UserItemBinding
import com.nomadics9.ananas.models.User
class UserLoginListAdapter(
private val clickListener: (user: User) -> Unit,
@ -48,4 +48,4 @@ class UserLoginListAdapter(
holder.itemView.setOnClickListener { clickListener(user) }
holder.bind(user)
}
}
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.View
@ -7,12 +7,12 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.bindItemImage
import dev.jdtech.jellyfin.databinding.BaseItemBinding
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.bindItemImage
import com.nomadics9.ananas.databinding.BaseItemBinding
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.isDownloaded
import com.nomadics9.ananas.core.R as CoreR
class ViewItemListAdapter(
private val onClickListener: (item: FindroidItem) -> Unit,

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.View
@ -7,12 +7,12 @@ import androidx.core.view.isVisible
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.bindItemImage
import dev.jdtech.jellyfin.databinding.BaseItemBinding
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.bindItemImage
import com.nomadics9.ananas.databinding.BaseItemBinding
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.isDownloaded
import com.nomadics9.ananas.core.R as CoreR
class ViewItemPagingAdapter(
private val onClickListener: (item: FindroidItem) -> Unit,
@ -70,4 +70,4 @@ class ViewItemPagingAdapter(
holder.bind(item, fixedWidth)
}
}
}
}

View file

@ -1,17 +1,17 @@
package dev.jdtech.jellyfin.adapters
package com.nomadics9.ananas.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import dev.jdtech.jellyfin.databinding.CardOfflineBinding
import dev.jdtech.jellyfin.databinding.NextUpSectionBinding
import dev.jdtech.jellyfin.databinding.ViewItemBinding
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.HomeItem
import dev.jdtech.jellyfin.models.View
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.databinding.CardOfflineBinding
import com.nomadics9.ananas.databinding.NextUpSectionBinding
import com.nomadics9.ananas.databinding.ViewItemBinding
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.HomeItem
import com.nomadics9.ananas.models.View
import com.nomadics9.ananas.core.R as CoreR
private const val ITEM_VIEW_TYPE_NEXT_UP = 0
private const val ITEM_VIEW_TYPE_VIEW = 1
@ -125,4 +125,4 @@ class ViewListAdapter(
is HomeItem.ViewItem -> ITEM_VIEW_TYPE_VIEW
}
}
}
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.di
package com.nomadics9.ananas.di
import android.content.Context
import dagger.Module
@ -6,7 +6,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import dev.jdtech.jellyfin.BaseApplication
import com.nomadics9.ananas.BaseApplication
import javax.inject.Singleton
@Module

View file

@ -1,11 +1,11 @@
package dev.jdtech.jellyfin.dialogs
package com.nomadics9.ananas.dialogs
import android.content.Context
import android.os.Environment
import android.os.StatFs
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
fun getStorageSelectionDialog(
context: Context,

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.text.method.LinkMovementMethod
@ -14,11 +14,12 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import com.nomadics9.ananas.BuildConfig
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.DiscoveredServerListAdapter
import dev.jdtech.jellyfin.databinding.FragmentAddServerBinding
import dev.jdtech.jellyfin.viewmodels.AddServerEvent
import dev.jdtech.jellyfin.viewmodels.AddServerViewModel
import com.nomadics9.ananas.adapters.DiscoveredServerListAdapter
import com.nomadics9.ananas.databinding.FragmentAddServerBinding
import com.nomadics9.ananas.viewmodels.AddServerEvent
import com.nomadics9.ananas.viewmodels.AddServerViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
@ -89,7 +90,12 @@ class AddServerFragment : Fragment() {
}
}
}
if (BuildConfig.FLAVOR == "Ananas") {
fun connectToServerDirectly(serverAddress: String = BuildConfig.DEFAULT_SERVER_ADDRESS) {
viewModel.checkServer(serverAddress.removeSuffix("/"))
}
connectToServerDirectly()
}
return binding.root
}
@ -134,6 +140,16 @@ class AddServerFragment : Fragment() {
viewModel.checkServer(serverAddress.removeSuffix("/"))
}
// private fun connectToServer() {
// val serverAddress = (binding.editTextServerAddress as AppCompatEditText).text.toString()
// if (serverAddress.isNotBlank()) {
// viewModel.checkServer(serverAddress.removeSuffix("/"))
// } else {
// viewModel.checkServer(BuildConfig.DEFAULT_SERVER_ADDRESS.removeSuffix("/"))
// }
// }
private fun navigateToLoginFragment() {
findNavController().navigate(AddServerFragmentDirections.actionAddServerFragmentToLoginFragment())
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -13,18 +13,18 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.databinding.FragmentFavoriteBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.CollectionViewModel
import com.nomadics9.ananas.adapters.FavoritesListAdapter
import com.nomadics9.ananas.databinding.FragmentFavoriteBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.FindroidShow
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.viewmodels.CollectionViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class CollectionFragment : Fragment() {

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -13,19 +13,19 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.databinding.FragmentDownloadsBinding
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.utils.restart
import dev.jdtech.jellyfin.viewmodels.DownloadsEvent
import dev.jdtech.jellyfin.viewmodels.DownloadsViewModel
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.adapters.FavoritesListAdapter
import com.nomadics9.ananas.databinding.FragmentDownloadsBinding
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.FindroidShow
import com.nomadics9.ananas.utils.restart
import com.nomadics9.ananas.viewmodels.DownloadsEvent
import com.nomadics9.ananas.viewmodels.DownloadsViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class DownloadsFragment : Fragment() {

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.app.DownloadManager
import android.os.Bundle
@ -21,23 +21,23 @@ import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.bindCardItemImage
import dev.jdtech.jellyfin.databinding.EpisodeBottomSheetBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.dialogs.getStorageSelectionDialog
import dev.jdtech.jellyfin.dialogs.getVideoVersionDialog
import dev.jdtech.jellyfin.models.FindroidSourceType
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.models.UiText
import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.models.isDownloading
import dev.jdtech.jellyfin.utils.setIconTintColorAttribute
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetEvent
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerItemsEvent
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.R
import com.nomadics9.ananas.bindCardItemImage
import com.nomadics9.ananas.databinding.EpisodeBottomSheetBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.dialogs.getStorageSelectionDialog
import com.nomadics9.ananas.dialogs.getVideoVersionDialog
import com.nomadics9.ananas.models.FindroidSourceType
import com.nomadics9.ananas.models.PlayerItem
import com.nomadics9.ananas.models.UiText
import com.nomadics9.ananas.models.isDownloaded
import com.nomadics9.ananas.models.isDownloading
import com.nomadics9.ananas.utils.setIconTintColorAttribute
import com.nomadics9.ananas.viewmodels.EpisodeBottomSheetEvent
import com.nomadics9.ananas.viewmodels.EpisodeBottomSheetViewModel
import com.nomadics9.ananas.viewmodels.PlayerItemsEvent
import com.nomadics9.ananas.viewmodels.PlayerViewModel
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.DateTime
import timber.log.Timber
@ -48,7 +48,7 @@ import java.util.UUID
import javax.inject.Inject
import android.R as AndroidR
import com.google.android.material.R as MaterialR
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -12,15 +12,15 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.databinding.FragmentFavoriteBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.FavoriteViewModel
import com.nomadics9.ananas.adapters.FavoritesListAdapter
import com.nomadics9.ananas.databinding.FragmentFavoriteBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.FindroidShow
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.viewmodels.FavoriteViewModel
import kotlinx.coroutines.launch
import timber.log.Timber

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -19,21 +19,21 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.adapters.ViewListAdapter
import dev.jdtech.jellyfin.databinding.FragmentHomeBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.utils.restart
import dev.jdtech.jellyfin.viewmodels.HomeViewModel
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.adapters.ViewListAdapter
import com.nomadics9.ananas.databinding.FragmentHomeBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.FindroidShow
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.utils.restart
import com.nomadics9.ananas.viewmodels.HomeViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class HomeFragment : Fragment() {
@ -74,6 +74,12 @@ class HomeFragment : Fragment() {
val searchView = search.actionView as SearchView
searchView.queryHint = getString(CoreR.string.search_hint)
val requests = menu.findItem(CoreR.id.action_requests)
requests.setOnMenuItemClickListener{
navigateToRequestsWebViewFragment()
true
}
search.setOnActionExpandListener(
object : MenuItem.OnActionExpandListener {
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
@ -202,7 +208,7 @@ class HomeFragment : Fragment() {
checkIfLoginRequired(uiState.error.message)
}
private fun navigateToLibraryFragment(view: dev.jdtech.jellyfin.models.View) {
private fun navigateToLibraryFragment(view: com.nomadics9.ananas.models.View) {
findNavController().navigate(
HomeFragmentDirections.actionNavigationHomeToLibraryFragment(
libraryId = view.id,
@ -251,4 +257,10 @@ class HomeFragment : Fragment() {
HomeFragmentDirections.actionHomeFragmentToSearchResultFragment(query),
)
}
private fun navigateToRequestsWebViewFragment() {
findNavController().navigate(
HomeFragmentDirections.actionHomeFragmentToRequestsWebFragment()
)
}
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -19,24 +19,25 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.paging.LoadState
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.adapters.ViewItemPagingAdapter
import dev.jdtech.jellyfin.databinding.FragmentLibraryBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.dialogs.SortDialogFragment
import dev.jdtech.jellyfin.models.FindroidBoxSet
import dev.jdtech.jellyfin.models.FindroidFolder
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.models.SortBy
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.adapters.ViewItemPagingAdapter
import com.nomadics9.ananas.databinding.FragmentLibraryBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.dialogs.SortDialogFragment
import com.nomadics9.ananas.models.FindroidBoxSet
import com.nomadics9.ananas.models.FindroidFolder
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.FindroidShow
import com.nomadics9.ananas.models.SortBy
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.viewmodels.LibraryViewModel
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.SortOrder
import java.lang.IllegalArgumentException
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
import androidx.recyclerview.widget.GridLayoutManager
@AndroidEntryPoint
class LibraryFragment : Fragment() {
@ -62,6 +63,11 @@ class LibraryFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.itemsRecyclerView.layoutManager =
GridLayoutManager(context, preferences.spanCount)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(
object : MenuProvider {

View file

@ -1,5 +1,7 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.Html.fromHtml
import android.view.LayoutInflater
@ -16,16 +18,18 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.adapters.UserLoginListAdapter
import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.databinding.FragmentLoginBinding
import dev.jdtech.jellyfin.viewmodels.LoginEvent
import dev.jdtech.jellyfin.viewmodels.LoginViewModel
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.BuildConfig
import com.nomadics9.ananas.adapters.UserLoginListAdapter
import com.nomadics9.ananas.database.ServerDatabaseDao
import com.nomadics9.ananas.databinding.FragmentLoginBinding
import com.nomadics9.ananas.viewmodels.LoginEvent
import com.nomadics9.ananas.viewmodels.LoginViewModel
import io.noties.markwon.Markwon
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class LoginFragment : Fragment() {
@ -78,6 +82,17 @@ class LoginFragment : Fragment() {
(binding.editTextPassword as AppCompatEditText).requestFocus()
}
if (BuildConfig.FLAVOR == "Ananas") {
binding.buttonForgetPassword.setOnClickListener {
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(BuildConfig.FORGET_PASSWORD_ADDRESS))
startActivity(browserIntent)
}
binding.buttonForgetPassword.isVisible = true
} else {
binding.buttonForgetPassword.isVisible = false
}
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
@ -143,7 +158,21 @@ class LoginFragment : Fragment() {
binding.editTextPasswordLayout.isEnabled = true
uiState.disclaimer?.let { disclaimer ->
binding.loginDisclaimer.text = fromHtml(disclaimer, 0)
if (BuildConfig.FLAVOR == "Ananas") {
val lines = disclaimer.lines()
val lineToRemoveIndex = 3
val filteredLines = lines.toMutableList().apply {
if (size > lineToRemoveIndex) {
removeAt(lineToRemoveIndex)
}
}
val filteredDisclaimer = filteredLines.joinToString("\n")
val markwon = Markwon.create(requireContext())
markwon.setMarkdown(binding.loginDisclaimer, filteredDisclaimer)
} else {
val markwon = Markwon.create(requireContext())
markwon.setMarkdown(binding.loginDisclaimer, disclaimer)
}
}
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -19,15 +19,15 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.CollectionListAdapter
import dev.jdtech.jellyfin.databinding.FragmentMediaBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.FindroidCollection
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.MediaViewModel
import com.nomadics9.ananas.adapters.CollectionListAdapter
import com.nomadics9.ananas.databinding.FragmentMediaBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.models.FindroidCollection
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.viewmodels.MediaViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class MediaFragment : Fragment() {

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.app.DownloadManager
import android.content.Intent
@ -21,32 +21,32 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.adapters.PersonListAdapter
import dev.jdtech.jellyfin.bindItemBackdropImage
import dev.jdtech.jellyfin.databinding.FragmentMovieBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.dialogs.getStorageSelectionDialog
import dev.jdtech.jellyfin.dialogs.getVideoVersionDialog
import dev.jdtech.jellyfin.models.AudioCodec
import dev.jdtech.jellyfin.models.DisplayProfile
import dev.jdtech.jellyfin.models.FindroidSourceType
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.models.UiText
import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.models.isDownloading
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.utils.setIconTintColorAttribute
import dev.jdtech.jellyfin.viewmodels.MovieEvent
import dev.jdtech.jellyfin.viewmodels.MovieViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerItemsEvent
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.R
import com.nomadics9.ananas.adapters.PersonListAdapter
import com.nomadics9.ananas.bindItemBackdropImage
import com.nomadics9.ananas.databinding.FragmentMovieBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.dialogs.getStorageSelectionDialog
import com.nomadics9.ananas.dialogs.getVideoVersionDialog
import com.nomadics9.ananas.models.AudioCodec
import com.nomadics9.ananas.models.DisplayProfile
import com.nomadics9.ananas.models.FindroidSourceType
import com.nomadics9.ananas.models.PlayerItem
import com.nomadics9.ananas.models.UiText
import com.nomadics9.ananas.models.isDownloaded
import com.nomadics9.ananas.models.isDownloading
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.utils.setIconTintColorAttribute
import com.nomadics9.ananas.viewmodels.MovieEvent
import com.nomadics9.ananas.viewmodels.MovieViewModel
import com.nomadics9.ananas.viewmodels.PlayerItemsEvent
import com.nomadics9.ananas.viewmodels.PlayerViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class MovieFragment : Fragment() {

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -15,18 +15,18 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.bindItemImage
import dev.jdtech.jellyfin.databinding.FragmentPersonDetailBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.PersonDetailViewModel
import com.nomadics9.ananas.adapters.ViewItemListAdapter
import com.nomadics9.ananas.bindItemImage
import com.nomadics9.ananas.databinding.FragmentPersonDetailBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.FindroidShow
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.viewmodels.PersonDetailViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
internal class PersonDetailFragment : Fragment() {

View file

@ -0,0 +1,72 @@
package com.nomadics9.ananas.fragments
import android.graphics.Bitmap
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.ProgressBar
import androidx.activity.OnBackPressedCallback
import androidx.fragment.app.Fragment
import com.nomadics9.ananas.BuildConfig
import com.nomadics9.ananas.R
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class RequestsWebViewFragment : Fragment() {
private lateinit var webView: WebView
private lateinit var progressBar: ProgressBar
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_webview, container, false)
webView = rootView.findViewById(R.id.webview)
progressBar = rootView.findViewById(R.id.progressBar)
val webSettings: WebSettings = webView.settings
webSettings.javaScriptEnabled = true // Enable JavaScript if required
// Set WebViewClient to handle loading URLs within the WebView
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
// Return false to indicate that the WebView should load the URL
return false
}
}
// Set up WebView client to handle page loading events
webView.webViewClient = object : WebViewClient() {
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
progressBar.visibility = View.VISIBLE // Show progress bar when page starts loading
}
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
progressBar.visibility = View.GONE // Hide progress bar when page finishes loading
}
}
// Load your URL here
webView.loadUrl(BuildConfig.REQUEST_SERVER_ADDRESS)
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
if (webView.canGoBack()) {
webView.goBack()
} else {
isEnabled = false
requireActivity().onBackPressed()
}
}
})
return rootView
}
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -13,15 +13,15 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.FavoritesListAdapter
import dev.jdtech.jellyfin.databinding.FragmentSearchResultBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.SearchResultViewModel
import com.nomadics9.ananas.adapters.FavoritesListAdapter
import com.nomadics9.ananas.databinding.FragmentSearchResultBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.FindroidShow
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.viewmodels.SearchResultViewModel
import kotlinx.coroutines.launch
import timber.log.Timber

View file

@ -0,0 +1,266 @@
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.view.MenuHost
import androidx.core.view.MenuProvider
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import com.nomadics9.ananas.adapters.EpisodeListAdapter
import com.nomadics9.ananas.databinding.FragmentSeasonBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.dialogs.getStorageSelectionDialog
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.viewmodels.SeasonEvent
import com.nomadics9.ananas.viewmodels.SeasonViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import androidx.appcompat.app.AlertDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.models.UiText
import javax.inject.Inject
@AndroidEntryPoint
class SeasonFragment : Fragment() {
private lateinit var binding: FragmentSeasonBinding
private val viewModel: SeasonViewModel by viewModels()
private val args: SeasonFragmentArgs by navArgs()
private lateinit var errorDialog: ErrorDialogFragment
private lateinit var downloadPreparingDialog: AlertDialog
@Inject
lateinit var appPreferences: AppPreferences
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = FragmentSeasonBinding.inflate(inflater, container, false)
val menuHost: MenuHost = requireActivity()
menuHost.addMenuProvider(
object : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(com.nomadics9.ananas.core.R.menu.season_menu, menu)
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
com.nomadics9.ananas.core.R.id.action_download_season -> {
if (requireContext().getExternalFilesDirs(null).filterNotNull().size > 1) {
val storageDialog = getStorageSelectionDialog(
requireContext(),
onItemSelected = { storageIndex ->
createEpisodesToDownloadDialog(storageIndex)
},
onCancel = {
},
)
viewModel.download()
return true
}
createEpisodesToDownloadDialog()
return true
}
else -> false
}
}
},
viewLifecycleOwner,
Lifecycle.State.RESUMED,
)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.uiState.collect { uiState ->
Timber.d("$uiState")
when (uiState) {
is SeasonViewModel.UiState.Normal -> bindUiStateNormal(uiState)
is SeasonViewModel.UiState.Loading -> bindUiStateLoading()
is SeasonViewModel.UiState.Error -> bindUiStateError(uiState)
}
}
}
launch {
viewModel.downloadStatus.collect { (status, progress) ->
when (status) {
10 -> {
downloadPreparingDialog.dismiss()
}
}
}
}
launch {
viewModel.downloadError.collect { uiText ->
createErrorDialog(uiText)
}
}
launch {
viewModel.eventsChannelFlow.collect { event ->
when (event) {
is SeasonEvent.NavigateBack -> findNavController().navigateUp()
}
}
}
}
}
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadEpisodes(args.seriesId, args.seasonId, args.offline)
}
binding.errorLayout.errorDetailsButton.setOnClickListener {
errorDialog.show(parentFragmentManager, ErrorDialogFragment.TAG)
}
binding.episodesRecyclerView.adapter =
EpisodeListAdapter { episode ->
navigateToEpisodeBottomSheetFragment(episode)
}
}
override fun onResume() {
super.onResume()
viewModel.loadEpisodes(args.seriesId, args.seasonId, args.offline)
}
private fun bindUiStateNormal(uiState: SeasonViewModel.UiState.Normal) {
uiState.apply {
val adapter = binding.episodesRecyclerView.adapter as EpisodeListAdapter
adapter.submitList(uiState.episodes)
}
binding.loadingIndicator.isVisible = false
binding.episodesRecyclerView.isVisible = true
binding.errorLayout.errorPanel.isVisible = false
}
private fun bindUiStateLoading() {
binding.loadingIndicator.isVisible = true
binding.errorLayout.errorPanel.isVisible = false
}
private fun bindUiStateError(uiState: SeasonViewModel.UiState.Error) {
errorDialog = ErrorDialogFragment.newInstance(uiState.error)
binding.loadingIndicator.isVisible = false
binding.episodesRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(uiState.error.message)
}
private fun createDownloadPreparingDialog() {
val builder = MaterialAlertDialogBuilder(requireContext())
downloadPreparingDialog = builder
.setTitle(com.nomadics9.ananas.core.R.string.preparing_download)
.setView(com.nomadics9.ananas.R.layout.preparing_download_dialog)
.setCancelable(false)
.create()
downloadPreparingDialog.show()
}
private fun createErrorDialog(uiText: UiText) {
val builder = MaterialAlertDialogBuilder(requireContext())
builder
.setTitle(com.nomadics9.ananas.core.R.string.downloading_error)
.setMessage(uiText.asString(requireContext().resources))
.setPositiveButton(getString(com.nomadics9.ananas.core.R.string.close)) { _, _ ->
}
builder.show()
}
private fun createEpisodesToDownloadDialog(storageIndex: Int = 0) {
if (!appPreferences.downloadQualityDefault)
createPickQualityDialog {
showDownloadDialog(storageIndex)
} else {
showDownloadDialog(storageIndex)
}
}
private fun showDownloadDialog(storageIndex: Int = 0) {
val builder = MaterialAlertDialogBuilder(requireContext())
val dialog = builder
.setTitle(com.nomadics9.ananas.core.R.string.download_season_dialog_title)
.setMessage(com.nomadics9.ananas.core.R.string.download_season_dialog_question)
.setPositiveButton(com.nomadics9.ananas.core.R.string.download_season_dialog_download_all) { _, _ ->
createDownloadPreparingDialog()
viewModel.download(storageIndex = storageIndex, downloadWatched = true)
}
.setNegativeButton(com.nomadics9.ananas.core.R.string.download_season_dialog_download_unwatched) { _, _ ->
createDownloadPreparingDialog()
viewModel.download(storageIndex = storageIndex, downloadWatched = false)
}
.create()
dialog.show()
}
private fun createPickQualityDialog(onQualitySelected: () -> Unit) {
val qualityEntries = resources.getStringArray(com.nomadics9.ananas.core.R.array.download_quality_entries)
val qualityValues = resources.getStringArray(com.nomadics9.ananas.core.R.array.download_quality_values)
val quality = appPreferences.downloadQuality
val currentQualityIndex = qualityValues.indexOf(quality)
var selectedQuality = quality
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setTitle("Download Quality")
builder.setSingleChoiceItems(qualityEntries, currentQualityIndex) { _, which ->
selectedQuality = qualityValues[which]
}
builder.setPositiveButton("Download") { dialog, _ ->
appPreferences.downloadQuality = selectedQuality
onQualitySelected()
dialog.dismiss()
}
builder.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
val dialog = builder.create()
dialog.show()
}
private fun navigateToEpisodeBottomSheetFragment(episode: FindroidEpisode) {
findNavController().navigate(
SeasonFragmentDirections.actionSeasonFragmentToEpisodeBottomSheetFragment(
episode.id,
),
)
}
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -12,12 +12,12 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.ServerAddressAdapter
import dev.jdtech.jellyfin.databinding.FragmentServerAddressesBinding
import dev.jdtech.jellyfin.dialogs.AddServerAddressDialog
import dev.jdtech.jellyfin.dialogs.DeleteServerAddressDialog
import dev.jdtech.jellyfin.viewmodels.ServerAddressesEvent
import dev.jdtech.jellyfin.viewmodels.ServerAddressesViewModel
import com.nomadics9.ananas.adapters.ServerAddressAdapter
import com.nomadics9.ananas.databinding.FragmentServerAddressesBinding
import com.nomadics9.ananas.dialogs.AddServerAddressDialog
import com.nomadics9.ananas.dialogs.DeleteServerAddressDialog
import com.nomadics9.ananas.viewmodels.ServerAddressesEvent
import com.nomadics9.ananas.viewmodels.ServerAddressesViewModel
import kotlinx.coroutines.launch
import timber.log.Timber

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -11,11 +11,11 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.ServerGridAdapter
import dev.jdtech.jellyfin.databinding.FragmentServerSelectBinding
import dev.jdtech.jellyfin.dialogs.DeleteServerDialogFragment
import dev.jdtech.jellyfin.viewmodels.ServerSelectEvent
import dev.jdtech.jellyfin.viewmodels.ServerSelectViewModel
import com.nomadics9.ananas.adapters.ServerGridAdapter
import com.nomadics9.ananas.databinding.FragmentServerSelectBinding
import com.nomadics9.ananas.dialogs.DeleteServerDialogFragment
import com.nomadics9.ananas.viewmodels.ServerSelectEvent
import com.nomadics9.ananas.viewmodels.ServerSelectViewModel
import kotlinx.coroutines.launch
import timber.log.Timber

View file

@ -1,11 +1,11 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import androidx.appcompat.app.AppCompatDelegate
import androidx.preference.ListPreference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
class SettingsAppearanceFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

View file

@ -1,10 +1,10 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.text.InputType
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
class SettingsCacheFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

View file

@ -1,12 +1,12 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import androidx.fragment.app.viewModels
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.viewmodels.SettingsDeviceViewModel
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.viewmodels.SettingsDeviceViewModel
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class SettingsDeviceFragment : PreferenceFragmentCompat() {

View file

@ -1,8 +1,8 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import androidx.preference.PreferenceFragmentCompat
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
class SettingsDownloadsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

View file

@ -0,0 +1,175 @@
package com.nomadics9.ananas.fragments
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.BuildConfig
import com.nomadics9.ananas.utils.restart
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.net.HttpURLConnection
import java.net.URL
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class SettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var appPreferences: AppPreferences
private val updateUrl = BuildConfig.UPDATE_ADDRESS
private var isUpdateAvailable: Boolean = false
private var newLastModifiedDate: Date? = null
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(CoreR.xml.fragment_settings, rootKey)
findPreference<Preference>("switchServer")?.setOnPreferenceClickListener {
findNavController().navigate(TwoPaneSettingsFragmentDirections.actionNavigationSettingsToServerSelectFragment())
true
}
findPreference<Preference>("switchUser")?.setOnPreferenceClickListener {
val serverId = appPreferences.currentServer!!
findNavController().navigate(
TwoPaneSettingsFragmentDirections.actionNavigationSettingsToUsersFragment(
serverId
)
)
true
}
findPreference<Preference>("switchAddress")?.setOnPreferenceClickListener {
val serverId = appPreferences.currentServer!!
findNavController().navigate(
TwoPaneSettingsFragmentDirections.actionNavigationSettingsToServerAddressesFragment(
serverId
)
)
true
}
findPreference<Preference>("pref_offline_mode")?.setOnPreferenceClickListener {
activity?.restart()
true
}
findPreference<Preference>("privacyPolicy")?.setOnPreferenceClickListener {
val intent = Intent(
Intent.ACTION_VIEW,
Uri.parse("https://github.com/nomadics9/ananas/blob/main/PRIVACY"),
)
startActivity(intent)
true
}
findPreference<Preference>("appInfo")?.setOnPreferenceClickListener {
if (isUpdateAvailable && newLastModifiedDate != null) {
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(updateUrl))
startActivity(intent)
storeDate(newLastModifiedDate!!)
true
} else {
findNavController().navigate(TwoPaneSettingsFragmentDirections.actionSettingsFragmentToAboutLibraries())
false
}
}
findPreference<Preference>("requests")?.setOnPreferenceClickListener {
findNavController().navigate(TwoPaneSettingsFragmentDirections.actionNavigationSettingsToRequestsWebFragment())
true
}
// Check for updates when the settings screen is opened
checkForUpdates()
}
private fun checkForUpdates() {
lifecycleScope.launch {
val lastModifiedDate = fetchLastModifiedDate(updateUrl)
if (lastModifiedDate != null) {
Timber.d("Fetched Last-Modified date: $lastModifiedDate")
val storedDate = getStoredDate()
Timber.d("Stored date: $storedDate")
if (storedDate == Date(0L) || lastModifiedDate.after(storedDate)) {
Timber.d("Update available")
isUpdateAvailable = true
newLastModifiedDate = lastModifiedDate
showUpdateAvailable()
} else {
Timber.d("No update available")
isUpdateAvailable = false
}
} else {
Timber.d("Failed to fetch Last-Modified date")
isUpdateAvailable = false
}
}
}
private suspend fun fetchLastModifiedDate(urlString: String): Date? {
return withContext(Dispatchers.IO) {
var urlConnection: HttpURLConnection? = null
try {
val url = URL(urlString)
urlConnection = url.openConnection() as HttpURLConnection
urlConnection.requestMethod = "HEAD"
val lastModified = urlConnection.getHeaderField("Last-Modified")
if (lastModified != null) {
val dateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US)
dateFormat.parse(lastModified)
} else {
null
}
} catch (e: Exception) {
Timber.e(e, "Error fetching Last-Modified date")
null
} finally {
urlConnection?.disconnect()
}
}
}
private fun getStoredDate(): Date {
val sharedPreferences = preferenceManager.sharedPreferences
val storedDateString = sharedPreferences?.getString("stored_date", null)
Timber.d("Retrieved stored date string: $storedDateString")
return if (storedDateString != null) {
try {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).parse(storedDateString) ?: Date(0)
} catch (e: Exception) {
Timber.e(e, "Error parsing stored date string")
Date(0)
}
} else {
Date(0)
}
}
private fun storeDate(date: Date) {
val dateString = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US).format(date)
preferenceManager.sharedPreferences?.edit()?.putString("stored_date", dateString)?.apply()
Timber.d("Stored new date: $dateString")
}
private fun showUpdateAvailable() {
val appInfoPreference = findPreference<Preference>("appInfo")
appInfoPreference?.let {
it.summary = "Update available!"
it.icon = ResourcesCompat.getDrawable(resources, CoreR.drawable.ic_download, null) // Ensure this drawable exists
Timber.d("Update available UI shown")
}
}
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.content.Intent
import android.net.Uri
@ -7,7 +7,7 @@ import android.os.Bundle
import android.provider.Settings
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
class SettingsLanguageFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

View file

@ -1,11 +1,11 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.text.InputType
import androidx.preference.EditTextPreference
import androidx.preference.PreferenceFragmentCompat
import dev.jdtech.jellyfin.Constants
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.Constants
import com.nomadics9.ananas.core.R as CoreR
class SettingsNetworkFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.content.Intent
import android.os.Bundle
@ -7,7 +7,7 @@ import android.text.InputType
import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
class SettingsPlayerFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.content.Intent
import android.net.Uri
@ -18,29 +18,29 @@ import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import com.google.android.material.R
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.adapters.PersonListAdapter
import dev.jdtech.jellyfin.adapters.ViewItemListAdapter
import dev.jdtech.jellyfin.bindCardItemImage
import dev.jdtech.jellyfin.bindItemBackdropImage
import dev.jdtech.jellyfin.databinding.FragmentShowBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidSeason
import dev.jdtech.jellyfin.models.FindroidSourceType
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.utils.setIconTintColorAttribute
import dev.jdtech.jellyfin.viewmodels.PlayerItemsEvent
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import dev.jdtech.jellyfin.viewmodels.ShowEvent
import dev.jdtech.jellyfin.viewmodels.ShowViewModel
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.adapters.PersonListAdapter
import com.nomadics9.ananas.adapters.ViewItemListAdapter
import com.nomadics9.ananas.bindCardItemImage
import com.nomadics9.ananas.bindItemBackdropImage
import com.nomadics9.ananas.databinding.FragmentShowBinding
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidSeason
import com.nomadics9.ananas.models.FindroidSourceType
import com.nomadics9.ananas.models.PlayerItem
import com.nomadics9.ananas.models.isDownloaded
import com.nomadics9.ananas.utils.checkIfLoginRequired
import com.nomadics9.ananas.utils.setIconTintColorAttribute
import com.nomadics9.ananas.viewmodels.PlayerItemsEvent
import com.nomadics9.ananas.viewmodels.PlayerViewModel
import com.nomadics9.ananas.viewmodels.ShowEvent
import com.nomadics9.ananas.viewmodels.ShowViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.UUID
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.core.R as CoreR
@AndroidEntryPoint
class ShowFragment : Fragment() {

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceHeaderFragmentCompat

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.fragments
package com.nomadics9.ananas.fragments
import android.os.Bundle
import android.view.LayoutInflater
@ -12,12 +12,12 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppNavigationDirections
import dev.jdtech.jellyfin.adapters.UserListAdapter
import dev.jdtech.jellyfin.databinding.FragmentUsersBinding
import dev.jdtech.jellyfin.dialogs.DeleteUserDialogFragment
import dev.jdtech.jellyfin.viewmodels.UsersEvent
import dev.jdtech.jellyfin.viewmodels.UsersViewModel
import com.nomadics9.ananas.AppNavigationDirections
import com.nomadics9.ananas.adapters.UserListAdapter
import com.nomadics9.ananas.databinding.FragmentUsersBinding
import com.nomadics9.ananas.dialogs.DeleteUserDialogFragment
import com.nomadics9.ananas.viewmodels.UsersEvent
import com.nomadics9.ananas.viewmodels.UsersViewModel
import kotlinx.coroutines.launch
import timber.log.Timber

View file

@ -1,8 +1,8 @@
package dev.jdtech.jellyfin.utils
package com.nomadics9.ananas.utils
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import dev.jdtech.jellyfin.AppNavigationDirections
import com.nomadics9.ananas.AppNavigationDirections
import timber.log.Timber
fun Fragment.checkIfLoginRequired(error: String?) {

View file

@ -1,7 +1,8 @@
package dev.jdtech.jellyfin.utils
package com.nomadics9.ananas.utils
import android.annotation.SuppressLint
import android.content.res.Resources
import android.graphics.Bitmap
import android.media.AudioManager
import android.os.Build
import android.os.SystemClock
@ -19,15 +20,20 @@ import android.view.animation.DecelerateInterpolator
import android.widget.ImageView
import androidx.media3.ui.AspectRatioFrameLayout
import androidx.media3.ui.PlayerView
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.Constants
import dev.jdtech.jellyfin.PlayerActivity
import dev.jdtech.jellyfin.isControlsLocked
import dev.jdtech.jellyfin.models.PlayerChapter
import dev.jdtech.jellyfin.mpv.MPVPlayer
import coil.transform.RoundedCornersTransformation
import com.nomadics9.ananas.AppPreferences
import com.nomadics9.ananas.Constants
import com.nomadics9.ananas.PlayerActivity
import com.nomadics9.ananas.isControlsLocked
import com.nomadics9.ananas.models.PlayerChapter
import com.nomadics9.ananas.models.Trickplay
import com.nomadics9.ananas.mpv.MPVPlayer
import timber.log.Timber
import kotlin.math.abs
import kotlinx.coroutines.Dispatchers
import coil.load
class PlayerGestureHelper(
private val appPreferences: AppPreferences,
private val activity: PlayerActivity,
@ -62,6 +68,10 @@ class PlayerGestureHelper(
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
private val screenHeight = Resources.getSystem().displayMetrics.heightPixels
var currentTrickplay: Trickplay? = null
private val roundedCorners = RoundedCornersTransformation(10f)
private var currentBitMap: Bitmap? = null
private var currentNumberOfPointers: Int = 0
private val tapGestureDetector = GestureDetector(
@ -265,6 +275,13 @@ class PlayerGestureHelper(
activity.binding.progressScrubberLayout.visibility = View.VISIBLE
activity.binding.progressScrubberText.text = "${longToTimestamp(difference)} [${longToTimestamp(newPos, true)}]"
swipeGestureValueTrackerProgress = newPos
if (currentTrickplay != null) {
onMove(newPos)
} else {
activity.binding.imagePreviewGesture.visibility = View.GONE
}
swipeGestureProgressOpen = true
true
} else {
@ -471,11 +488,28 @@ class PlayerGestureHelper(
return false
}
fun onMove(position: Long) {
val trickplay = currentTrickplay ?: return
val image = trickplay.images[position.div(trickplay.interval).toInt()]
if (currentBitMap != image) {
activity.binding.imagePreviewGesture.load(image) {
dispatcher(Dispatchers.Main.immediate)
transformations(roundedCorners)
}
currentBitMap = image
}
}
init {
if (appPreferences.playerBrightnessRemember) {
activity.window.attributes.screenBrightness = appPreferences.playerBrightness
}
if (!appPreferences.playerTrickPlayGesture) {
activity.binding.imagePreviewGesture.visibility = View.GONE
}
updateZoomMode(appPreferences.playerStartMaximized)
@Suppress("ClickableViewAccessibility")

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.utils
package com.nomadics9.ananas.utils
import android.graphics.Bitmap
import android.view.View
@ -8,7 +8,7 @@ import androidx.media3.common.Player
import androidx.media3.ui.TimeBar
import coil.load
import coil.transform.RoundedCornersTransformation
import dev.jdtech.jellyfin.models.Trickplay
import com.nomadics9.ananas.models.Trickplay
import kotlinx.coroutines.Dispatchers
import timber.log.Timber

View file

@ -1,120 +0,0 @@
package dev.jdtech.jellyfin.fragments
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.EpisodeListAdapter
import dev.jdtech.jellyfin.databinding.FragmentSeasonBinding
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.SeasonEvent
import dev.jdtech.jellyfin.viewmodels.SeasonViewModel
import kotlinx.coroutines.launch
import timber.log.Timber
@AndroidEntryPoint
class SeasonFragment : Fragment() {
private lateinit var binding: FragmentSeasonBinding
private val viewModel: SeasonViewModel by viewModels()
private val args: SeasonFragmentArgs by navArgs()
private lateinit var errorDialog: ErrorDialogFragment
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = FragmentSeasonBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.uiState.collect { uiState ->
Timber.d("$uiState")
when (uiState) {
is SeasonViewModel.UiState.Normal -> bindUiStateNormal(uiState)
is SeasonViewModel.UiState.Loading -> bindUiStateLoading()
is SeasonViewModel.UiState.Error -> bindUiStateError(uiState)
}
}
}
launch {
viewModel.eventsChannelFlow.collect { event ->
when (event) {
is SeasonEvent.NavigateBack -> findNavController().navigateUp()
}
}
}
}
}
binding.errorLayout.errorRetryButton.setOnClickListener {
viewModel.loadEpisodes(args.seriesId, args.seasonId, args.offline)
}
binding.errorLayout.errorDetailsButton.setOnClickListener {
errorDialog.show(parentFragmentManager, ErrorDialogFragment.TAG)
}
binding.episodesRecyclerView.adapter =
EpisodeListAdapter { episode ->
navigateToEpisodeBottomSheetFragment(episode)
}
}
override fun onResume() {
super.onResume()
viewModel.loadEpisodes(args.seriesId, args.seasonId, args.offline)
}
private fun bindUiStateNormal(uiState: SeasonViewModel.UiState.Normal) {
uiState.apply {
val adapter = binding.episodesRecyclerView.adapter as EpisodeListAdapter
adapter.submitList(uiState.episodes)
}
binding.loadingIndicator.isVisible = false
binding.episodesRecyclerView.isVisible = true
binding.errorLayout.errorPanel.isVisible = false
}
private fun bindUiStateLoading() {
binding.loadingIndicator.isVisible = true
binding.errorLayout.errorPanel.isVisible = false
}
private fun bindUiStateError(uiState: SeasonViewModel.UiState.Error) {
errorDialog = ErrorDialogFragment.newInstance(uiState.error)
binding.loadingIndicator.isVisible = false
binding.episodesRecyclerView.isVisible = false
binding.errorLayout.errorPanel.isVisible = true
checkIfLoginRequired(uiState.error.message)
}
private fun navigateToEpisodeBottomSheetFragment(episode: FindroidEpisode) {
findNavController().navigate(
SeasonFragmentDirections.actionSeasonFragmentToEpisodeBottomSheetFragment(
episode.id,
),
)
}
}

View file

@ -1,59 +0,0 @@
package dev.jdtech.jellyfin.fragments
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.utils.restart
import javax.inject.Inject
import dev.jdtech.jellyfin.core.R as CoreR
@AndroidEntryPoint
class SettingsFragment : PreferenceFragmentCompat() {
@Inject
lateinit var appPreferences: AppPreferences
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(CoreR.xml.fragment_settings, rootKey)
findPreference<Preference>("switchServer")?.setOnPreferenceClickListener {
findNavController().navigate(TwoPaneSettingsFragmentDirections.actionNavigationSettingsToServerSelectFragment())
true
}
findPreference<Preference>("switchUser")?.setOnPreferenceClickListener {
val serverId = appPreferences.currentServer!!
findNavController().navigate(TwoPaneSettingsFragmentDirections.actionNavigationSettingsToUsersFragment(serverId))
true
}
findPreference<Preference>("switchAddress")?.setOnPreferenceClickListener {
val serverId = appPreferences.currentServer!!
findNavController().navigate(TwoPaneSettingsFragmentDirections.actionNavigationSettingsToServerAddressesFragment(serverId))
true
}
findPreference<Preference>("pref_offline_mode")?.setOnPreferenceClickListener {
activity?.restart()
true
}
findPreference<Preference>("privacyPolicy")?.setOnPreferenceClickListener {
val intent = Intent(
Intent.ACTION_VIEW,
Uri.parse("https://github.com/jarnedemeulemeester/findroid/blob/main/PRIVACY"),
)
startActivity(intent)
true
}
findPreference<Preference>("appInfo")?.setOnPreferenceClickListener {
findNavController().navigate(TwoPaneSettingsFragmentDirections.actionSettingsFragmentToAboutLibraries())
true
}
}
}

View file

@ -37,7 +37,16 @@
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/main_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
android:layout_height="?attr/actionBarSize">
<ImageView
android:layout_width="65dp"
android:layout_height="match_parent"
android:layout_gravity="center"
android:contentDescription="@string/app_name"
android:src="@drawable/ic_launcher_foreground" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>

View file

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -39,8 +40,17 @@
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/main_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
android:layout_height="?attr/actionBarSize">
<ImageView
android:layout_width="65dp"
android:layout_height="match_parent"
android:layout_gravity="center"
android:contentDescription="@string/app_name"
android:src="@drawable/ic_launcher_foreground" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -16,8 +16,11 @@
<LinearLayout
android:id="@+id/progress_scrubber_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="64dp"
android:padding="10dp"
android:background="@drawable/overlay_background"
android:clickable="false"
android:gravity="center"
@ -25,10 +28,20 @@
android:visibility="gone"
tools:visibility="visible">
<ImageView
android:id="@+id/image_preview_gesture"
android:layout_width="272dp"
android:layout_height="153dp"
android:layout_gravity="center"
android:layout_marginBottom="10dp"
android:contentDescription="@string/player_trickplay" />
<TextView
android:id="@+id/progress_scrubber_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:textAppearance="@style/TextAppearance.Material3.HeadlineSmall"
android:textColor="@android:color/white"
tools:text="+00:00:10 [00:00:20]" />

View file

@ -10,6 +10,7 @@
android:layout_height="match_parent"
android:layout_gravity="center">
<!-- Video surface will be inserted as the first child of the content frame. -->
<View
@ -48,24 +49,6 @@
android:textColor="@android:color/white"
android:textSize="14sp" />
<Button
android:id="@+id/btn_skip_intro"
style="@style/Widget.Material3.Button.OutlinedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_marginEnd="24dp"
android:layout_marginBottom="64dp"
android:text="@string/player_controls_skip_intro"
android:textColor="@android:color/white"
android:visibility="gone"
app:backgroundTint="@color/player_background"
app:icon="@drawable/ic_skip_forward"
app:iconGravity="end"
app:iconTint="@android:color/white"
app:strokeColor="@android:color/white"
tools:visibility="visible" />
</androidx.media3.ui.AspectRatioFrameLayout>
<androidx.media3.ui.SubtitleView
@ -89,4 +72,33 @@
android:layout_height="match_parent"
app:animation_enabled="false"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="end|bottom"
android:layout_marginEnd="24dp"
android:layout_marginBottom="64dp">
<Button
android:id="@+id/btn_watch_credits"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:text="@string/watch_credits"
android:visibility="gone"
tools:visibility="visible" />
<Button
android:id="@+id/btn_skip_intro"
style="@style/Widget.Material3.Button.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:icon="@drawable/ic_skip_forward"
tools:visibility="visible" />
</LinearLayout>
</merge>

View file

@ -141,10 +141,24 @@
android:visibility="invisible" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp">
<Button
android:id="@+id/button_forget_password"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/forget_password" />
</RelativeLayout>
<TextView
android:id="@+id/login_disclaimer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginHorizontal="24dp"
android:layout_margin="24dp"
android:textSize="16sp"

View file

@ -0,0 +1,16 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>

View file

@ -7,7 +7,7 @@
<fragment
android:id="@+id/homeFragment"
android:name="dev.jdtech.jellyfin.fragments.HomeFragment"
android:name="com.nomadics9.ananas.fragments.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home">
<action
@ -49,11 +49,14 @@
<action
android:id="@+id/action_homeFragment_to_searchResultFragment"
app:destination="@id/searchResultFragment" />
<action
android:id="@+id/action_homeFragment_to_requestsWebFragment"
app:destination="@id/requestsWebFragment" />
</fragment>
<fragment
android:id="@+id/mediaFragment"
android:name="dev.jdtech.jellyfin.fragments.MediaFragment"
android:name="com.nomadics9.ananas.fragments.MediaFragment"
android:label="@string/title_media"
tools:layout="@layout/fragment_media">
<action
@ -70,7 +73,7 @@
<fragment
android:id="@+id/twoPaneSettingsFragment"
android:name="dev.jdtech.jellyfin.fragments.TwoPaneSettingsFragment"
android:name="com.nomadics9.ananas.fragments.TwoPaneSettingsFragment"
android:label="@string/title_settings">
<action
android:id="@+id/action_navigation_settings_to_serverSelectFragment"
@ -84,13 +87,23 @@
<action
android:id="@+id/action_settingsFragment_to_about_libraries"
app:destination="@id/about_libraries" />
<action
android:id="@+id/action_navigation_settings_to_requestsWebFragment"
app:destination="@id/requestsWebFragment" />
</fragment>
<fragment
android:id="@+id/settingsFragment"
android:name="dev.jdtech.jellyfin.fragments.SettingsFragment" />
android:name="com.nomadics9.ananas.fragments.SettingsFragment">
</fragment>
<fragment
android:id="@+id/requestsWebFragment"
android:name="com.nomadics9.ananas.fragments.RequestsWebViewFragment"
android:label="Requests"
tools:layout="@layout/fragment_webview">
</fragment>
<fragment
android:id="@+id/libraryFragment"
android:name="dev.jdtech.jellyfin.fragments.LibraryFragment"
android:name="com.nomadics9.ananas.fragments.LibraryFragment"
android:label="{libraryName}"
tools:layout="@layout/fragment_library">
<argument
@ -122,14 +135,14 @@
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
<argument
android:name="libraryType"
app:argType="dev.jdtech.jellyfin.models.CollectionType" />
app:argType="com.nomadics9.ananas.models.CollectionType" />
<action
android:id="@+id/action_libraryFragment_self"
app:destination="@id/libraryFragment" />
</fragment>
<fragment
android:id="@+id/showFragment"
android:name="dev.jdtech.jellyfin.fragments.ShowFragment"
android:name="com.nomadics9.ananas.fragments.ShowFragment"
android:label="{itemName}"
tools:layout="@layout/fragment_show">
<argument
@ -158,7 +171,7 @@
<fragment
android:id="@+id/movieFragment"
android:name="dev.jdtech.jellyfin.fragments.MovieFragment"
android:name="com.nomadics9.ananas.fragments.MovieFragment"
android:label="{itemName}"
tools:layout="@layout/fragment_movie">
<argument
@ -179,7 +192,7 @@
<fragment
android:id="@+id/seasonFragment"
android:name="dev.jdtech.jellyfin.fragments.SeasonFragment"
android:name="com.nomadics9.ananas.fragments.SeasonFragment"
android:label="{seasonName}"
tools:layout="@layout/fragment_season">
<argument
@ -211,7 +224,7 @@
</fragment>
<dialog
android:id="@+id/episodeBottomSheetFragment"
android:name="dev.jdtech.jellyfin.fragments.EpisodeBottomSheetFragment"
android:name="com.nomadics9.ananas.fragments.EpisodeBottomSheetFragment"
android:label="EpisodeBottomSheetFragment"
tools:layout="@layout/episode_bottom_sheet">
<argument
@ -226,7 +239,7 @@
</dialog>
<fragment
android:id="@+id/favoriteFragment"
android:name="dev.jdtech.jellyfin.fragments.FavoriteFragment"
android:name="com.nomadics9.ananas.fragments.FavoriteFragment"
android:label="@string/title_favorite"
tools:layout="@layout/fragment_favorite">
<action
@ -241,7 +254,7 @@
</fragment>
<fragment
android:id="@+id/collectionFragment"
android:name="dev.jdtech.jellyfin.fragments.CollectionFragment"
android:name="com.nomadics9.ananas.fragments.CollectionFragment"
android:label="{collectionName}"
tools:layout="@layout/fragment_favorite">
<argument
@ -264,7 +277,7 @@
</fragment>
<fragment
android:id="@+id/searchResultFragment"
android:name="dev.jdtech.jellyfin.fragments.SearchResultFragment"
android:name="com.nomadics9.ananas.fragments.SearchResultFragment"
android:label="{query}"
tools:layout="@layout/fragment_search_result">
<action
@ -282,7 +295,7 @@
</fragment>
<fragment
android:id="@+id/addServerFragment"
android:name="dev.jdtech.jellyfin.fragments.AddServerFragment"
android:name="com.nomadics9.ananas.fragments.AddServerFragment"
android:label="@string/add_server"
tools:layout="@layout/fragment_add_server">
<action
@ -291,7 +304,7 @@
</fragment>
<fragment
android:id="@+id/serverSelectFragment"
android:name="dev.jdtech.jellyfin.fragments.ServerSelectFragment"
android:name="com.nomadics9.ananas.fragments.ServerSelectFragment"
android:label="@string/select_server"
tools:layout="@layout/fragment_server_select">
<action
@ -308,7 +321,7 @@
</fragment>
<fragment
android:id="@+id/loginFragment"
android:name="dev.jdtech.jellyfin.fragments.LoginFragment"
android:name="com.nomadics9.ananas.fragments.LoginFragment"
android:label="@string/login"
tools:layout="@layout/fragment_login">
<action
@ -324,7 +337,7 @@
<fragment
android:id="@+id/personDetailFragment"
android:name="dev.jdtech.jellyfin.fragments.PersonDetailFragment"
android:name="com.nomadics9.ananas.fragments.PersonDetailFragment"
android:label="@string/person_detail_title"
tools:layout="@layout/fragment_person_detail">
@ -342,12 +355,12 @@
<activity
android:id="@+id/playerActivity"
android:name="dev.jdtech.jellyfin.PlayerActivity"
android:name="com.nomadics9.ananas.PlayerActivity"
android:label="activity_player"
tools:layout="@layout/activity_player">
<argument
android:name="items"
app:argType="dev.jdtech.jellyfin.models.PlayerItem[]" />
app:argType="com.nomadics9.ananas.models.PlayerItem[]" />
</activity>
<include app:graph="@navigation/aboutlibs_navigation" />
@ -357,7 +370,7 @@
<fragment
android:id="@+id/usersFragment"
android:name="dev.jdtech.jellyfin.fragments.UsersFragment"
android:name="com.nomadics9.ananas.fragments.UsersFragment"
android:label="@string/users"
tools:layout="@layout/fragment_users">
<action
@ -375,7 +388,7 @@
<fragment
android:id="@+id/serverAddressesFragment"
android:name="dev.jdtech.jellyfin.fragments.ServerAddressesFragment"
android:name="com.nomadics9.ananas.fragments.ServerAddressesFragment"
android:label="@string/addresses"
tools:layout="@layout/fragment_server_addresses">
<action
@ -390,7 +403,7 @@
<fragment
android:id="@+id/downloadsFragment"
android:name="dev.jdtech.jellyfin.fragments.DownloadsFragment"
android:name="com.nomadics9.ananas.fragments.DownloadsFragment"
android:label="@string/title_download"
tools:layout="@layout/fragment_favorite">
<action
@ -409,4 +422,4 @@
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
</fragment>
</navigation>
</navigation>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="watch_credits">Watch credits</string>
</resources>

View file

@ -9,12 +9,12 @@ plugins {
}
android {
namespace = "dev.jdtech.jellyfin"
namespace = "com.nomadics9.ananas"
compileSdk = Versions.compileSdk
buildToolsVersion = Versions.buildTools
defaultConfig {
applicationId = "dev.jdtech.jellyfin"
applicationId = "com.nomadics9.ananas"
minSdk = Versions.minSdk
targetSdk = Versions.targetSdk
@ -81,14 +81,15 @@ ktlint {
}
dependencies {
val composeBom = platform(libs.androidx.compose.bom)
implementation(projects.core)
implementation(projects.data)
implementation(projects.preferences)
implementation(projects.player.core)
implementation(projects.player.video)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.runtime)
implementation(composeBom)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.core)
@ -98,6 +99,7 @@ dependencies {
implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.session)
implementation(libs.androidx.paging.compose)
implementation(libs.androidx.tv.foundation)
implementation(libs.androidx.tv.material)
implementation(libs.coil.compose)
implementation(libs.coil.svg)

View file

@ -0,0 +1,65 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.nomadics9.ananas.debug",
"variantName": "libreDebug",
"elements": [
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 6,
"versionName": "0.14.2",
"outputFile": "tv-libre-armeabi-v7a-debug.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 6,
"versionName": "0.14.2",
"outputFile": "tv-libre-x86_64-debug.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "arm64-v8a"
}
],
"attributes": [],
"versionCode": 6,
"versionName": "0.14.2",
"outputFile": "tv-libre-arm64-v8a-debug.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86"
}
],
"attributes": [],
"versionCode": 6,
"versionName": "0.14.2",
"outputFile": "tv-libre-x86-debug.apk"
}
],
"elementType": "File",
"minSdkVersionForDexing": 28
}

View file

@ -0,0 +1,87 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.nomadics9.ananas",
"variantName": "libreRelease",
"elements": [
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 6,
"versionName": "0.14.2",
"outputFile": "tv-libre-armeabi-v7a-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86"
}
],
"attributes": [],
"versionCode": 6,
"versionName": "0.14.2",
"outputFile": "tv-libre-x86-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "arm64-v8a"
}
],
"attributes": [],
"versionCode": 6,
"versionName": "0.14.2",
"outputFile": "tv-libre-arm64-v8a-release.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 6,
"versionName": "0.14.2",
"outputFile": "tv-libre-x86_64-release.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/tv-libre-armeabi-v7a-release.dm",
"baselineProfiles/1/tv-libre-x86-release.dm",
"baselineProfiles/1/tv-libre-arm64-v8a-release.dm",
"baselineProfiles/1/tv-libre-x86_64-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/tv-libre-armeabi-v7a-release.dm",
"baselineProfiles/0/tv-libre-x86-release.dm",
"baselineProfiles/0/tv-libre-arm64-v8a-release.dm",
"baselineProfiles/0/tv-libre-x86_64-release.dm"
]
}
],
"minSdkVersionForDexing": 28
}

View file

@ -0,0 +1,87 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.nomadics9.ananas.staging",
"variantName": "libreStaging",
"elements": [
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "armeabi-v7a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "0.14.2",
"outputFile": "tv-libre-armeabi-v7a-staging.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86_64"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "0.14.2",
"outputFile": "tv-libre-x86_64-staging.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "x86"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "0.14.2",
"outputFile": "tv-libre-x86-staging.apk"
},
{
"type": "ONE_OF_MANY",
"filters": [
{
"filterType": "ABI",
"value": "arm64-v8a"
}
],
"attributes": [],
"versionCode": 1,
"versionName": "0.14.2",
"outputFile": "tv-libre-arm64-v8a-staging.apk"
}
],
"elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/tv-libre-armeabi-v7a-staging.dm",
"baselineProfiles/1/tv-libre-x86_64-staging.dm",
"baselineProfiles/1/tv-libre-x86-staging.dm",
"baselineProfiles/1/tv-libre-arm64-v8a-staging.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/tv-libre-armeabi-v7a-staging.dm",
"baselineProfiles/0/tv-libre-x86_64-staging.dm",
"baselineProfiles/0/tv-libre-x86-staging.dm",
"baselineProfiles/0/tv-libre-arm64-v8a-staging.dm"
]
}
],
"minSdkVersionForDexing": 28
}

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.app.Application
import coil.ImageLoader

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.os.Bundle
import androidx.activity.ComponentActivity
@ -6,11 +6,11 @@ import androidx.activity.compose.setContent
import androidx.activity.viewModels
import com.ramcosta.composedestinations.DestinationsNavHost
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.destinations.AddServerScreenDestination
import dev.jdtech.jellyfin.destinations.LoginScreenDestination
import dev.jdtech.jellyfin.ui.theme.FindroidTheme
import dev.jdtech.jellyfin.viewmodels.MainViewModel
import com.nomadics9.ananas.database.ServerDatabaseDao
import com.nomadics9.ananas.destinations.AddServerScreenDestination
import com.nomadics9.ananas.destinations.LoginScreenDestination
import com.nomadics9.ananas.ui.theme.FindroidTheme
import com.nomadics9.ananas.viewmodels.MainViewModel
import javax.inject.Inject
@AndroidEntryPoint

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin
package com.nomadics9.ananas
import android.os.Bundle
import android.view.WindowManager
@ -9,11 +9,11 @@ import com.ramcosta.composedestinations.annotation.ActivityDestination
import com.ramcosta.composedestinations.manualcomposablecalls.composable
import com.ramcosta.composedestinations.scope.resultRecipient
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.destinations.PlayerActivityDestination
import dev.jdtech.jellyfin.destinations.PlayerScreenDestination
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.ui.PlayerScreen
import dev.jdtech.jellyfin.ui.theme.FindroidTheme
import com.nomadics9.ananas.destinations.PlayerActivityDestination
import com.nomadics9.ananas.destinations.PlayerScreenDestination
import com.nomadics9.ananas.models.PlayerItem
import com.nomadics9.ananas.ui.PlayerScreen
import com.nomadics9.ananas.ui.theme.FindroidTheme
data class PlayerActivityNavArgs(
val items: ArrayList<PlayerItem>,

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.ui
package com.nomadics9.ananas.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -38,13 +38,13 @@ import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import dev.jdtech.jellyfin.destinations.LoginScreenDestination
import dev.jdtech.jellyfin.ui.theme.FindroidTheme
import dev.jdtech.jellyfin.ui.theme.spacings
import dev.jdtech.jellyfin.utils.ObserveAsEvents
import dev.jdtech.jellyfin.viewmodels.AddServerEvent
import dev.jdtech.jellyfin.viewmodels.AddServerViewModel
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.destinations.LoginScreenDestination
import com.nomadics9.ananas.ui.theme.FindroidTheme
import com.nomadics9.ananas.ui.theme.spacings
import com.nomadics9.ananas.utils.ObserveAsEvents
import com.nomadics9.ananas.viewmodels.AddServerEvent
import com.nomadics9.ananas.viewmodels.AddServerViewModel
import com.nomadics9.ananas.core.R as CoreR
@Destination
@Composable
@ -115,7 +115,7 @@ private fun AddServerScreenLayout(
},
singleLine = true,
keyboardOptions = KeyboardOptions(
autoCorrectEnabled = false,
autoCorrect = false,
keyboardType = KeyboardType.Uri,
imeAction = ImeAction.Go,
),

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.ui
package com.nomadics9.ananas.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
@ -6,9 +6,6 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -22,28 +19,31 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.foundation.lazy.list.TvLazyRow
import androidx.tv.foundation.lazy.list.items
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import dev.jdtech.jellyfin.destinations.MovieScreenDestination
import dev.jdtech.jellyfin.destinations.PlayerActivityDestination
import dev.jdtech.jellyfin.destinations.ShowScreenDestination
import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.models.HomeItem
import dev.jdtech.jellyfin.ui.components.Direction
import dev.jdtech.jellyfin.ui.components.ItemCard
import dev.jdtech.jellyfin.ui.dummy.dummyHomeItems
import dev.jdtech.jellyfin.ui.theme.FindroidTheme
import dev.jdtech.jellyfin.ui.theme.spacings
import dev.jdtech.jellyfin.utils.ObserveAsEvents
import dev.jdtech.jellyfin.viewmodels.HomeViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerItemsEvent
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.destinations.MovieScreenDestination
import com.nomadics9.ananas.destinations.PlayerActivityDestination
import com.nomadics9.ananas.destinations.ShowScreenDestination
import com.nomadics9.ananas.models.FindroidEpisode
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.FindroidShow
import com.nomadics9.ananas.models.HomeItem
import com.nomadics9.ananas.ui.components.Direction
import com.nomadics9.ananas.ui.components.ItemCard
import com.nomadics9.ananas.ui.dummy.dummyHomeItems
import com.nomadics9.ananas.ui.theme.FindroidTheme
import com.nomadics9.ananas.ui.theme.spacings
import com.nomadics9.ananas.utils.ObserveAsEvents
import com.nomadics9.ananas.viewmodels.HomeViewModel
import com.nomadics9.ananas.viewmodels.PlayerItemsEvent
import com.nomadics9.ananas.viewmodels.PlayerViewModel
import com.nomadics9.ananas.core.R as CoreR
@Destination
@Composable
@ -107,7 +107,7 @@ private fun HomeScreenLayout(
}
else -> Unit
}
LazyColumn(
TvLazyColumn(
contentPadding = PaddingValues(bottom = MaterialTheme.spacings.large),
modifier = Modifier
.fillMaxSize()
@ -122,7 +122,7 @@ private fun HomeScreenLayout(
modifier = Modifier.padding(start = MaterialTheme.spacings.large),
)
Spacer(modifier = Modifier.height(MaterialTheme.spacings.medium))
LazyRow(
TvLazyRow(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacings.default),
contentPadding = PaddingValues(horizontal = MaterialTheme.spacings.large),
) {
@ -145,7 +145,7 @@ private fun HomeScreenLayout(
modifier = Modifier.padding(start = MaterialTheme.spacings.large),
)
Spacer(modifier = Modifier.height(MaterialTheme.spacings.medium))
LazyRow(
TvLazyRow(
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacings.default),
contentPadding = PaddingValues(horizontal = MaterialTheme.spacings.large),
) {

View file

@ -1,10 +1,7 @@
package dev.jdtech.jellyfin.ui
package com.nomadics9.ananas.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -17,18 +14,21 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.tv.foundation.lazy.grid.TvGridCells
import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid
import androidx.tv.foundation.lazy.grid.items
import androidx.tv.material3.MaterialTheme
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import dev.jdtech.jellyfin.destinations.LibraryScreenDestination
import dev.jdtech.jellyfin.models.CollectionType
import dev.jdtech.jellyfin.models.FindroidCollection
import dev.jdtech.jellyfin.ui.components.Direction
import dev.jdtech.jellyfin.ui.components.ItemCard
import dev.jdtech.jellyfin.ui.dummy.dummyCollections
import dev.jdtech.jellyfin.ui.theme.FindroidTheme
import dev.jdtech.jellyfin.ui.theme.spacings
import dev.jdtech.jellyfin.viewmodels.MediaViewModel
import com.nomadics9.ananas.destinations.LibraryScreenDestination
import com.nomadics9.ananas.models.CollectionType
import com.nomadics9.ananas.models.FindroidCollection
import com.nomadics9.ananas.ui.components.Direction
import com.nomadics9.ananas.ui.components.ItemCard
import com.nomadics9.ananas.ui.dummy.dummyCollections
import com.nomadics9.ananas.ui.theme.FindroidTheme
import com.nomadics9.ananas.ui.theme.spacings
import com.nomadics9.ananas.viewmodels.MediaViewModel
import java.util.UUID
@Destination
@ -72,8 +72,8 @@ private fun LibrariesScreenLayout(
val focusRequester = remember { FocusRequester() }
LazyVerticalGrid(
columns = GridCells.Fixed(3),
TvLazyVerticalGrid(
columns = TvGridCells.Fixed(3),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacings.large),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacings.large),
contentPadding = PaddingValues(

View file

@ -1,11 +1,8 @@
package dev.jdtech.jellyfin.ui
package com.nomadics9.ananas.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@ -18,24 +15,27 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.tv.foundation.lazy.grid.TvGridCells
import androidx.tv.foundation.lazy.grid.TvGridItemSpan
import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import dev.jdtech.jellyfin.destinations.LibraryScreenDestination
import dev.jdtech.jellyfin.destinations.MovieScreenDestination
import dev.jdtech.jellyfin.destinations.ShowScreenDestination
import dev.jdtech.jellyfin.models.CollectionType
import dev.jdtech.jellyfin.models.FindroidFolder
import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.ui.components.Direction
import dev.jdtech.jellyfin.ui.components.ItemCard
import dev.jdtech.jellyfin.ui.dummy.dummyMovies
import dev.jdtech.jellyfin.ui.theme.FindroidTheme
import dev.jdtech.jellyfin.ui.theme.spacings
import dev.jdtech.jellyfin.viewmodels.LibraryViewModel
import com.nomadics9.ananas.destinations.LibraryScreenDestination
import com.nomadics9.ananas.destinations.MovieScreenDestination
import com.nomadics9.ananas.destinations.ShowScreenDestination
import com.nomadics9.ananas.models.CollectionType
import com.nomadics9.ananas.models.FindroidFolder
import com.nomadics9.ananas.models.FindroidItem
import com.nomadics9.ananas.models.FindroidMovie
import com.nomadics9.ananas.models.FindroidShow
import com.nomadics9.ananas.ui.components.Direction
import com.nomadics9.ananas.ui.components.ItemCard
import com.nomadics9.ananas.ui.dummy.dummyMovies
import com.nomadics9.ananas.ui.theme.FindroidTheme
import com.nomadics9.ananas.ui.theme.spacings
import com.nomadics9.ananas.viewmodels.LibraryViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import java.util.UUID
@ -86,8 +86,8 @@ private fun LibraryScreenLayout(
is LibraryViewModel.UiState.Loading -> Text(text = "LOADING")
is LibraryViewModel.UiState.Normal -> {
val items = uiState.items.collectAsLazyPagingItems()
LazyVerticalGrid(
columns = GridCells.Fixed(5),
TvLazyVerticalGrid(
columns = TvGridCells.Fixed(5),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacings.default),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.spacings.default),
contentPadding = PaddingValues(horizontal = MaterialTheme.spacings.default * 2, vertical = MaterialTheme.spacings.large),
@ -95,7 +95,7 @@ private fun LibraryScreenLayout(
.fillMaxSize()
.focusRequester(focusRequester),
) {
item(span = { GridItemSpan(this.maxLineSpan) }) {
item(span = { TvGridItemSpan(this.maxLineSpan) }) {
Text(
text = libraryName,
style = MaterialTheme.typography.displayMedium,

View file

@ -1,4 +1,4 @@
package dev.jdtech.jellyfin.ui
package com.nomadics9.ananas.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -41,15 +41,15 @@ import androidx.tv.material3.Text
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import com.ramcosta.composedestinations.navigation.popUpTo
import dev.jdtech.jellyfin.NavGraphs
import dev.jdtech.jellyfin.destinations.MainScreenDestination
import dev.jdtech.jellyfin.models.UiText
import dev.jdtech.jellyfin.ui.theme.FindroidTheme
import dev.jdtech.jellyfin.ui.theme.spacings
import dev.jdtech.jellyfin.utils.ObserveAsEvents
import dev.jdtech.jellyfin.viewmodels.LoginEvent
import dev.jdtech.jellyfin.viewmodels.LoginViewModel
import dev.jdtech.jellyfin.core.R as CoreR
import com.nomadics9.ananas.NavGraphs
import com.nomadics9.ananas.destinations.MainScreenDestination
import com.nomadics9.ananas.models.UiText
import com.nomadics9.ananas.ui.theme.FindroidTheme
import com.nomadics9.ananas.ui.theme.spacings
import com.nomadics9.ananas.utils.ObserveAsEvents
import com.nomadics9.ananas.viewmodels.LoginEvent
import com.nomadics9.ananas.viewmodels.LoginViewModel
import com.nomadics9.ananas.core.R as CoreR
@Destination
@Composable
@ -152,7 +152,7 @@ private fun LoginScreenLayout(
label = { Text(text = stringResource(id = CoreR.string.edit_text_username_hint)) },
singleLine = true,
keyboardOptions = KeyboardOptions(
autoCorrectEnabled = false,
autoCorrect = false,
keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next,
),
@ -175,7 +175,7 @@ private fun LoginScreenLayout(
label = { Text(text = stringResource(id = CoreR.string.edit_text_password_hint)) },
singleLine = true,
keyboardOptions = KeyboardOptions(
autoCorrectEnabled = false,
autoCorrect = false,
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Go,
),

Some files were not shown because too many files have changed in this diff Show more