diff --git a/.github/workflows/assemble-test-lint.yml b/.github/workflows/assemble-test-lint.yml
index 26a84ca156..02fcec2309 100644
--- a/.github/workflows/assemble-test-lint.yml
+++ b/.github/workflows/assemble-test-lint.yml
@@ -21,11 +21,11 @@ jobs:
steps:
- name: Check out
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - name: Set up JDK 17
- uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - name: Set up JDK 21
+ uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with:
- java-version: '17'
+ java-version: '21'
distribution: 'temurin'
cache: gradle
@@ -53,11 +53,11 @@ jobs:
steps:
- name: Check out
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- - name: Set up JDK 17
- uses: actions/setup-java@99b8673ff64fbf99d8d325f52d9a5bdedb8483e9 # v4.2.1
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
+ - name: Set up JDK 21
+ uses: actions/setup-java@8df1039502a15bceb9433410b1a100fbe190c53b # v4.5.0
with:
- java-version: '17'
+ java-version: '21'
distribution: 'temurin'
cache: gradle
@@ -73,7 +73,7 @@ jobs:
run: ./gradlew :app:lintAmazonRelease
- name: Upload lint reports
- uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
+ uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
if: ${{ always() }} # also upload of lint errors
with:
name: Lint reports
diff --git a/.gitignore b/.gitignore
index b758183295..df1056c0cc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,12 +41,16 @@ $RECYCLE.BIN/
!.idea/codeStyleSettings.xml
!.idea/codeStyles
!.idea/copyright
+!.idea/inspectionProfiles
!.idea/scopes
### Gradle
.gradle/
build/
+## Kotlin
+.kotlin/
+
## Proguard
unused.txt
diff --git a/.idea/inspectionProfiles/Android_Lint.xml b/.idea/inspectionProfiles/Android_Lint.xml
new file mode 100644
index 0000000000..8d350b24dd
--- /dev/null
+++ b/.idea/inspectionProfiles/Android_Lint.xml
@@ -0,0 +1,1004 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000000..5b8a5a9b9e
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e07e54b8c9..3976f81541 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,47 @@
📝 = Notable change.
Releases marked with 🧪 (or previously with the "beta" suffix) were released on
-[the beta program](https://github.com/UweTrottmann/SeriesGuide/wiki/Beta) only.
+[the preview program](https://www.seriesgui.de/help/how-to/basics/preview) only.
+
+## Version 2024.5
+
+* 🌟 Shows: add a note to a show, synced with SeriesGuide Cloud or Trakt (VIP only).
+* 🌟 Movies: add link to all release dates.
+* 🔧 Shows: increase resolution of episode images.
+
+### 2024.5.4 - 2024-12-13
+
+* 🔨 Trakt: retry if show not yet in Trakt profile failed to upload during initial sync.
+* 📝 Latest user interface translations from Crowdin.
+
+### 2024.5.3 - 2024-12-11 🧪
+
+* 🔧 Shows: revert to search symbol for primary button on discover screen.
+* 🔧 Lists: ask for confirmation before deleting a list, actually call it delete instead of "just"
+ remove.
+* 📝 Latest user interface translations from Crowdin.
+
+### 2024.5.2 - 2024-12-04 🧪
+
+* 🔧 Shows: when viewing the stream or purchase provider filters, the reset button is shown as
+ disabled when no provider is selected. Also tabs are renamed and display icons to differentiate
+ filter from sort options.
+* 🔨 Show scrollbars for show filter and sort options.
+* 🔧 Use common "Sort by" action name.
+* 🔧 Android 15: turn predictive back animation back on after more issues are resolved.
+* 📝 Latest user interface translations from Crowdin.
+
+### 2024.5.1 - 2024-11-21 🧪
+
+* 🌟 Shows: add a note to a show, synced with SeriesGuide Cloud or Trakt (VIP only).
+* 🔧 Shows: increase resolution of episode images.
+* 🔧 Shows: also use plus symbol for button on discover screen to be consistent.
+
+### 2024.5.0 - 2024-11-06 🧪
+
+* 🌟 Movies: add link to all release dates.
+* 🔧 Overview: use local number format for absolute episode number.
+* 📝 Latest user interface translations from Crowdin.
## Version 2024.4
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 1d11f86b33..7cf88e4dc7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,28 +1,32 @@
# Contributing
-**Note:** This work is licensed under the [Apache License 2.0](LICENSE.txt).
-If you contribute any
+ℹ️ This work is licensed under the [Apache License 2.0](LICENSE.txt).
+If you contribute any
[non-trivial](http://www.gnu.org/prep/maintain/maintain.html#Legally-Significant)
patches or translations make sure you have read it and agree with it.
-#### Would you like to contribute code?
+**Would you like to contribute code?**
+
+ℹ️ If you want to contribute larger changes, please talk to me first (comment on a related issue
+or create one). Otherwise, it is likely I won't accept your merge request.
1. [Fork SeriesGuide](https://github.com/UweTrottmann/SeriesGuide/fork) and clone your fork.
-2. See the notes about [building](#building) the app below.
+2. See the notes about [building](#building) the app below. Take the [guidelines of this project](/docs/guidelines.md) into account.
3. Create a new branch ([using GitHub](https://help.github.com/articles/creating-and-deleting-branches-within-your-repository/)
or the command `git checkout -b descriptive-branch-name dev`).
4. Make [great commits](http://robots.thoughtbot.com/post/48933156625/5-useful-tips-for-a-better-commit-message). For non-trivial changes, add a copyright line at the top of the files you edited.
5. [Start a pull request](https://github.com/UweTrottmann/SeriesGuide/compare) and reference [issues](https://github.com/UweTrottmann/SeriesGuide/issues) if needed.
-#### No code!
-* You can [discuss or submit bug reports](https://github.com/UweTrottmann/SeriesGuide/issues).
-* You can [suggest features](https://discuss.seriesgui.de).
-* You can [translate the app](https://crowdin.com/project/seriesguide-translations).
+**No code!**
+
+- You can [discuss or submit bug reports](https://github.com/UweTrottmann/SeriesGuide/issues).
+- You can [suggest features](https://discuss.seriesgui.de).
+- You can [translate the app](https://crowdin.com/project/seriesguide-translations).
## Building
-- `dev` is the main development and [test release](https://github.com/UweTrottmann/SeriesGuide/wiki/Beta) branch.
-- `main` has always the latest [stable version](https://seriesgui.de).
+- `dev` contains the latest changes.
+- `main` contains the latest stable version.
To get started:
@@ -34,20 +38,23 @@ To get started:
Debug builds should just work.
-### TMDB, trakt
-To add shows or movies you need to create an API key for [TMDB](https://www.themoviedb.org/settings/api)
-and OAuth credentials for [trakt](https://trakt.tv/oauth/applications).
+### TMDB, Trakt
+
+To add shows or movies you need to create an API key for [TMDB](https://www.themoviedb.org/settings/api)
+and OAuth credentials for [Trakt](https://trakt.tv/oauth/applications).
Place them in `secret.properties` in the project directory (where `settings.gradle` is):
-```
+```text
SG_TMDB_API_KEY=
SG_TRAKT_CLIENT_ID=
SG_TRAKT_CLIENT_SECRET=
```
### Release
+
To release some additional `secret.properties` values might be necessary:
-```
+
+```text
# Play Store in-app billing public key
SG_IAP_KEY_A=
SG_IAP_KEY_B=
diff --git a/README.md b/README.md
index 6d1bb99144..2c5146a543 100644
--- a/README.md
+++ b/README.md
@@ -5,14 +5,14 @@ Android app to help you keep track of your favorite TV shows and movies.
- Download •
- Support the dev •
+ Download •
+ Support the dev •
Contributing •
Announcements & Help
-
+
diff --git a/RELEASING.md b/RELEASING.md
index b83a2a5b01..9c1cfdaecf 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -1,12 +1,13 @@
# Release process
-- If stable, create `release-` branch. If beta, stay on `dev`.
-- Optional: Update translations.
-- Change version code and name in [`build.gradle.kts`](/build.gradle.kts).
-- Amend [`CHANGELOG.md`](/CHANGELOG.md).
-- Deploy to test device.
-- Push to GitHub and check build succeeds, tests are green and Lint file is OK.
-
+- If it does not exist, create a `release-` branch
+- Merge latest changes from `dev`
+- Optional: Update translations
+- Change version code and name in [`build.gradle.kts`](/build.gradle.kts)
+- Update [`CHANGELOG.md`](/CHANGELOG.md)
+- Push to GitHub
+- If it does not exist, create a merge request against `main`
+- Check build succeeds, tests are green and lint output is as expected
## Play Store (testing + production)
@@ -14,13 +15,14 @@
- Publish to alpha channel, test.
Published to beta channel:
+
- Tag like `v12.0.3`.
Published to production:
-- Download universal APK from Play Store and attach to GitHub tag.
+- Download universal APK from Play Store and attach to GitHub tag.
-## Amazon App Store (stable only)
+## Amazon App Store (production only)
-- `assembleAmazonRelease`
+- `bundleAmazonRelease`
- Test update on test device.
diff --git a/api/build.gradle.kts b/api/build.gradle.kts
index 78f8caf668..1c26a31191 100644
--- a/api/build.gradle.kts
+++ b/api/build.gradle.kts
@@ -11,6 +11,11 @@ val sgCompileSdk: Int by rootProject.extra
val sgMinSdk: Int by rootProject.extra
val sgTargetSdk: Int by rootProject.extra
+tasks.withType(JavaCompile::class.java).configureEach {
+ // Suppress JDK 21 warning about deprecated, but not yet removed, source and target value 8 support
+ options.compilerArgs.add("-Xlint:-options")
+}
+
android {
namespace = "com.battlelancer.seriesguide.api"
compileSdk = sgCompileSdk
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index fc68ea694a..2b19970b07 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -5,6 +5,7 @@ plugins {
id("com.android.application")
kotlin("android")
kotlin("kapt")
+ alias(libs.plugins.compose.compiler)
}
if (project.file("google-services.json").exists()) {
@@ -21,6 +22,11 @@ val sgTargetSdk: Int by rootProject.extra
val sgVersionCode: Int by rootProject.extra
val sgVersionName: String by rootProject.extra
+tasks.withType(JavaCompile::class.java).configureEach {
+ // Suppress JDK 21 warning about deprecated, but not yet removed, source and target value 8 support
+ options.compilerArgs.add("-Xlint:-options")
+}
+
android {
namespace = "com.battlelancer.seriesguide"
compileSdk = sgCompileSdk
@@ -29,7 +35,7 @@ android {
buildFeatures {
buildConfig = true
- // https://developer.android.com/jetpack/compose/interop/adding
+ // https://developer.android.com/develop/ui/compose/setup
compose = true
// https://firebase.google.com/support/release-notes/android
viewBinding = true
@@ -39,6 +45,7 @@ android {
minSdk = sgMinSdk
targetSdk = sgTargetSdk
+ // Prevent plugin from generating PNGs, use compat loading instead https://developer.android.com/studio/write/vector-asset-studio#sloption
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -65,16 +72,12 @@ android {
targetCompatibility = JavaVersion.VERSION_1_8
}
- composeOptions {
- // https://developer.android.com/jetpack/androidx/releases/compose-kotlin
- kotlinCompilerExtensionVersion = "1.5.14" // For Kotlin 1.9.24
- }
-
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
// Using experimental flatMapLatest for Paging 3
// Using experimental Material 3 compose APIs
- freeCompilerArgs = freeCompilerArgs + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi,androidx.compose.material3.ExperimentalMaterial3Api"
+ freeCompilerArgs =
+ freeCompilerArgs + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi,androidx.compose.material3.ExperimentalMaterial3Api"
}
lint {
@@ -95,7 +98,8 @@ android {
productFlavors {
create("pure") {
- isDefault = true // Make Studio select this by default, it often resets (after updates, randomly)
+ // Make Studio select this by default, it often resets (after updates, randomly)
+ isDefault = true
applicationId = "com.battlelancer.seriesguide"
versionCode = sgVersionCode
@@ -155,6 +159,9 @@ android {
// Based on https://docs.oracle.com/en/java/javase/17/docs/specs/jar/jar.html#jar-index
// only used by network applications like applets, so safe to exclude.
excludes += "/META-INF/INDEX.LIST"
+ // Exclude Coroutines debug file
+ // https://github.com/Kotlin/kotlinx.coroutines?tab=readme-ov-file#avoiding-including-the-debug-infrastructure-in-the-resulting-apk
+ excludes += "DebugProbesKt.bin"
}
}
}
@@ -164,7 +171,6 @@ kapt {
arguments {
arg("eventBusIndex", "com.battlelancer.seriesguide.SgEventBusIndex")
arg("room.schemaLocation", "$projectDir/schemas")
- arg("room.incremental", "true")
}
}
@@ -204,7 +210,7 @@ dependencies {
// Optional - Integration with activities
implementation(libs.androidx.activity.compose)
// Optional - Integration with ViewModels
- implementation( libs.androidx.lifecycle.compose)
+ implementation(libs.androidx.lifecycle.compose)
// ViewModel and LiveData
implementation(libs.androidx.lifecycle.livedata)
@@ -219,6 +225,8 @@ dependencies {
implementation(libs.androidx.room.ktx)
// Paging 3 Integration
implementation(libs.androidx.room.paging)
+ // KSP appears deprecated. KSP 2 is still under development.
+ //noinspection KaptUsageInsteadOfKsp
kapt(libs.androidx.room.compiler)
implementation(libs.dagger)
diff --git a/app/schemas/com.battlelancer.seriesguide.provider.SgRoomDatabase/54.json b/app/schemas/com.battlelancer.seriesguide.provider.SgRoomDatabase/54.json
new file mode 100644
index 0000000000..4bd82e33fc
--- /dev/null
+++ b/app/schemas/com.battlelancer.seriesguide.provider.SgRoomDatabase/54.json
@@ -0,0 +1,1653 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 54,
+ "identityHash": "37e698b27a2494936bc264752cbf9943",
+ "entities": [
+ {
+ "tableName": "series",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER NOT NULL, `series_slug` TEXT, `seriestitle` TEXT NOT NULL, `series_title_noarticle` TEXT, `overview` TEXT, `airstime` INTEGER, `airsdayofweek` INTEGER, `series_airtime` TEXT, `series_timezone` TEXT, `firstaired` TEXT, `genres` TEXT, `network` TEXT, `rating` REAL, `series_rating_votes` INTEGER, `series_rating_user` INTEGER, `runtime` TEXT, `status` TEXT, `contentrating` TEXT, `next` TEXT, `poster` TEXT, `series_poster_small` TEXT, `series_nextairdate` INTEGER, `nexttext` TEXT, `imdbid` TEXT, `series_trakt_id` INTEGER, `series_favorite` INTEGER NOT NULL, `series_syncenabled` INTEGER NOT NULL, `series_hidden` INTEGER NOT NULL, `series_lastupdate` INTEGER NOT NULL, `series_lastedit` INTEGER NOT NULL, `series_lastwatchedid` INTEGER NOT NULL, `series_lastwatched_ms` INTEGER NOT NULL, `series_language` TEXT, `series_unwatched_count` INTEGER NOT NULL, `series_notify` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
+ "fields": [
+ {
+ "fieldPath": "tvdbId",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "slug",
+ "columnName": "series_slug",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "seriestitle",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "titleNoArticle",
+ "columnName": "series_title_noarticle",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "overview",
+ "columnName": "overview",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "releaseTime",
+ "columnName": "airstime",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "releaseWeekDay",
+ "columnName": "airsdayofweek",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "releaseCountry",
+ "columnName": "series_airtime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "releaseTimeZone",
+ "columnName": "series_timezone",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstRelease",
+ "columnName": "firstaired",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "genres",
+ "columnName": "genres",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "network",
+ "columnName": "network",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingGlobal",
+ "columnName": "rating",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingVotes",
+ "columnName": "series_rating_votes",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingUser",
+ "columnName": "series_rating_user",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "runtime",
+ "columnName": "runtime",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "status",
+ "columnName": "status",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "contentRating",
+ "columnName": "contentrating",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextEpisode",
+ "columnName": "next",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "poster",
+ "columnName": "poster",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "posterSmall",
+ "columnName": "series_poster_small",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextAirdateMs",
+ "columnName": "series_nextairdate",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextText",
+ "columnName": "nexttext",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "imdbId",
+ "columnName": "imdbid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "traktId",
+ "columnName": "series_trakt_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "favorite",
+ "columnName": "series_favorite",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hexagonMergeComplete",
+ "columnName": "series_syncenabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hidden",
+ "columnName": "series_hidden",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdatedMs",
+ "columnName": "series_lastupdate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastEditedSec",
+ "columnName": "series_lastedit",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastWatchedEpisodeId",
+ "columnName": "series_lastwatchedid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastWatchedMs",
+ "columnName": "series_lastwatched_ms",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "language",
+ "columnName": "series_language",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unwatchedCount",
+ "columnName": "series_unwatched_count",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notify",
+ "columnName": "series_notify",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "seasons",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER, `combinednr` INTEGER, `series_id` TEXT, `watchcount` INTEGER, `willaircount` INTEGER, `noairdatecount` INTEGER, `seasonposter` TEXT, `season_totalcount` INTEGER, PRIMARY KEY(`_id`), FOREIGN KEY(`series_id`) REFERENCES `series`(`_id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "tvdbId",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "combinednr",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "showTvdbId",
+ "columnName": "series_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "watchCount",
+ "columnName": "watchcount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "notReleasedCount",
+ "columnName": "willaircount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "noReleaseDateCount",
+ "columnName": "noairdatecount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tags",
+ "columnName": "seasonposter",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "totalCount",
+ "columnName": "season_totalcount",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_seasons_series_id",
+ "unique": false,
+ "columnNames": [
+ "series_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_seasons_series_id` ON `${TABLE_NAME}` (`series_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "series",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "series_id"
+ ],
+ "referencedColumns": [
+ "_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "episodes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER NOT NULL, `episodetitle` TEXT NOT NULL, `episodedescription` TEXT, `episodenumber` INTEGER NOT NULL, `season` INTEGER NOT NULL, `dvdnumber` REAL, `season_id` INTEGER NOT NULL, `series_id` INTEGER NOT NULL, `watched` INTEGER NOT NULL, `plays` INTEGER, `directors` TEXT, `gueststars` TEXT, `writers` TEXT, `episodeimage` TEXT, `episode_firstairedms` INTEGER NOT NULL, `episode_collected` INTEGER NOT NULL, `rating` REAL, `episode_rating_votes` INTEGER, `episode_rating_user` INTEGER, `episode_imdbid` TEXT, `episode_lastedit` INTEGER NOT NULL, `absolute_number` INTEGER, `episode_lastupdate` INTEGER NOT NULL, PRIMARY KEY(`_id`), FOREIGN KEY(`season_id`) REFERENCES `seasons`(`_id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`series_id`) REFERENCES `series`(`_id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "tvdbId",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "episodetitle",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "overview",
+ "columnName": "episodedescription",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "episodenumber",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "season",
+ "columnName": "season",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dvdNumber",
+ "columnName": "dvdnumber",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "seasonTvdbId",
+ "columnName": "season_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "showTvdbId",
+ "columnName": "series_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "watched",
+ "columnName": "watched",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "plays",
+ "columnName": "plays",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "directors",
+ "columnName": "directors",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "guestStars",
+ "columnName": "gueststars",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "writers",
+ "columnName": "writers",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "image",
+ "columnName": "episodeimage",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstReleasedMs",
+ "columnName": "episode_firstairedms",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "collected",
+ "columnName": "episode_collected",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ratingGlobal",
+ "columnName": "rating",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingVotes",
+ "columnName": "episode_rating_votes",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingUser",
+ "columnName": "episode_rating_user",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "imdbId",
+ "columnName": "episode_imdbid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastEditedSec",
+ "columnName": "episode_lastedit",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absoluteNumber",
+ "columnName": "absolute_number",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastUpdatedSec",
+ "columnName": "episode_lastupdate",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_episodes_season_id",
+ "unique": false,
+ "columnNames": [
+ "season_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_episodes_season_id` ON `${TABLE_NAME}` (`season_id`)"
+ },
+ {
+ "name": "index_episodes_series_id",
+ "unique": false,
+ "columnNames": [
+ "series_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_episodes_series_id` ON `${TABLE_NAME}` (`series_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "seasons",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "season_id"
+ ],
+ "referencedColumns": [
+ "_id"
+ ]
+ },
+ {
+ "table": "series",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "series_id"
+ ],
+ "referencedColumns": [
+ "_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "sg_show",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `series_tmdb_id` INTEGER, `series_tvdb_id` INTEGER, `series_slug` TEXT, `series_trakt_id` INTEGER, `series_title` TEXT NOT NULL, `series_title_noarticle` TEXT, `series_overview` TEXT, `series_airstime` INTEGER, `series_airsdayofweek` INTEGER, `series_country` TEXT, `series_timezone` TEXT, `series_firstaired` TEXT, `series_genres` TEXT, `series_network` TEXT, `series_imdbid` TEXT, `series_rating_tmdb` REAL, `series_rating_tmdb_votes` INTEGER, `series_rating` REAL, `series_rating_votes` INTEGER, `series_rating_user` INTEGER, `series_runtime` INTEGER, `series_status` INTEGER, `series_contentrating` TEXT, `series_next` TEXT, `series_poster` TEXT, `series_poster_small` TEXT, `series_nextairdate` INTEGER, `series_nexttext` TEXT, `series_lastupdate` INTEGER NOT NULL, `series_lastedit` INTEGER NOT NULL, `series_lastwatchedid` INTEGER NOT NULL, `series_lastwatched_ms` INTEGER NOT NULL, `series_language` TEXT, `series_unwatched_count` INTEGER NOT NULL, `series_favorite` INTEGER NOT NULL, `series_hidden` INTEGER NOT NULL, `series_notify` INTEGER NOT NULL, `series_syncenabled` INTEGER NOT NULL, `series_custom_release_time` INTEGER, `series_custom_day_offset` INTEGER, `series_custom_timezone` TEXT, `series_user_note` TEXT, `series_user_note_trakt_id` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tmdbId",
+ "columnName": "series_tmdb_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tvdbId",
+ "columnName": "series_tvdb_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "slug",
+ "columnName": "series_slug",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "traktId",
+ "columnName": "series_trakt_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "series_title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "titleNoArticle",
+ "columnName": "series_title_noarticle",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "overview",
+ "columnName": "series_overview",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "releaseTime",
+ "columnName": "series_airstime",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "releaseWeekDay",
+ "columnName": "series_airsdayofweek",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "releaseCountry",
+ "columnName": "series_country",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "releaseTimeZone",
+ "columnName": "series_timezone",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstRelease",
+ "columnName": "series_firstaired",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "genres",
+ "columnName": "series_genres",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "network",
+ "columnName": "series_network",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "imdbId",
+ "columnName": "series_imdbid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingTmdb",
+ "columnName": "series_rating_tmdb",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingTmdbVotes",
+ "columnName": "series_rating_tmdb_votes",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingTrakt",
+ "columnName": "series_rating",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingTraktVotes",
+ "columnName": "series_rating_votes",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingUser",
+ "columnName": "series_rating_user",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "runtime",
+ "columnName": "series_runtime",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "status",
+ "columnName": "series_status",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "contentRating",
+ "columnName": "series_contentrating",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextEpisode",
+ "columnName": "series_next",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "poster",
+ "columnName": "series_poster",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "posterSmall",
+ "columnName": "series_poster_small",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextAirdateMs",
+ "columnName": "series_nextairdate",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "nextText",
+ "columnName": "series_nexttext",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastUpdatedMs",
+ "columnName": "series_lastupdate",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastEditedSec",
+ "columnName": "series_lastedit",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastWatchedEpisodeId",
+ "columnName": "series_lastwatchedid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastWatchedMs",
+ "columnName": "series_lastwatched_ms",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "language",
+ "columnName": "series_language",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "unwatchedCount",
+ "columnName": "series_unwatched_count",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "favorite",
+ "columnName": "series_favorite",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hidden",
+ "columnName": "series_hidden",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notify",
+ "columnName": "series_notify",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "hexagonMergeComplete",
+ "columnName": "series_syncenabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "customReleaseTime",
+ "columnName": "series_custom_release_time",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "customReleaseDayOffset",
+ "columnName": "series_custom_day_offset",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "customReleaseTimeZone",
+ "columnName": "series_custom_timezone",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "userNote",
+ "columnName": "series_user_note",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "userNoteTraktId",
+ "columnName": "series_user_note_trakt_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_sg_show_series_tmdb_id",
+ "unique": false,
+ "columnNames": [
+ "series_tmdb_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_sg_show_series_tmdb_id` ON `${TABLE_NAME}` (`series_tmdb_id`)"
+ },
+ {
+ "name": "index_sg_show_series_tvdb_id",
+ "unique": false,
+ "columnNames": [
+ "series_tvdb_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_sg_show_series_tvdb_id` ON `${TABLE_NAME}` (`series_tvdb_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "sg_season",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `series_id` INTEGER NOT NULL, `season_tmdb_id` TEXT, `season_tvdb_id` INTEGER, `season_number` INTEGER, `season_name` TEXT, `season_order` INTEGER NOT NULL, `season_watchcount` INTEGER, `season_willaircount` INTEGER, `season_noairdatecount` INTEGER, `season_totalcount` INTEGER, `season_tags` TEXT, FOREIGN KEY(`series_id`) REFERENCES `sg_show`(`_id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "showId",
+ "columnName": "series_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tmdbId",
+ "columnName": "season_tmdb_id",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tvdbId",
+ "columnName": "season_tvdb_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "numberOrNull",
+ "columnName": "season_number",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "season_name",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "season_order",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "notWatchedReleasedOrNull",
+ "columnName": "season_watchcount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "notWatchedToBeReleasedOrNull",
+ "columnName": "season_willaircount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "notWatchedNoReleaseOrNull",
+ "columnName": "season_noairdatecount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "totalOrNull",
+ "columnName": "season_totalcount",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tags",
+ "columnName": "season_tags",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_sg_season_series_id",
+ "unique": false,
+ "columnNames": [
+ "series_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_sg_season_series_id` ON `${TABLE_NAME}` (`series_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "sg_show",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "series_id"
+ ],
+ "referencedColumns": [
+ "_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "sg_episode",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `season_id` INTEGER NOT NULL, `series_id` INTEGER NOT NULL, `episode_tmdb_id` INTEGER, `episode_tvdb_id` INTEGER, `episode_title` TEXT, `episode_description` TEXT, `episode_number` INTEGER NOT NULL, `episode_absolute_number` INTEGER, `episode_season_number` INTEGER NOT NULL, `episode_order` INTEGER NOT NULL, `episode_dvd_number` REAL, `episode_watched` INTEGER NOT NULL, `episode_plays` INTEGER, `episode_collected` INTEGER NOT NULL, `episode_directors` TEXT, `episode_gueststars` TEXT, `episode_writers` TEXT, `episode_image` TEXT, `episode_firstairedms` INTEGER NOT NULL, `episode_rating_tmdb` REAL, `episode_rating_tmdb_votes` INTEGER, `episode_rating` REAL, `episode_rating_votes` INTEGER, `episode_rating_user` INTEGER, `episode_imdbid` TEXT, `episode_lastedit` INTEGER NOT NULL, `episode_lastupdate` INTEGER NOT NULL, FOREIGN KEY(`series_id`) REFERENCES `sg_show`(`_id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "seasonId",
+ "columnName": "season_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "showId",
+ "columnName": "series_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tmdbId",
+ "columnName": "episode_tmdb_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tvdbId",
+ "columnName": "episode_tvdb_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "episode_title",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "overview",
+ "columnName": "episode_description",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "number",
+ "columnName": "episode_number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "absoluteNumber",
+ "columnName": "episode_absolute_number",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "season",
+ "columnName": "episode_season_number",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "episode_order",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "dvdNumber",
+ "columnName": "episode_dvd_number",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "watched",
+ "columnName": "episode_watched",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "plays",
+ "columnName": "episode_plays",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "collected",
+ "columnName": "episode_collected",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directors",
+ "columnName": "episode_directors",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "guestStars",
+ "columnName": "episode_gueststars",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "writers",
+ "columnName": "episode_writers",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "image",
+ "columnName": "episode_image",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "firstReleasedMs",
+ "columnName": "episode_firstairedms",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "ratingTmdb",
+ "columnName": "episode_rating_tmdb",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingTmdbVotes",
+ "columnName": "episode_rating_tmdb_votes",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingTrakt",
+ "columnName": "episode_rating",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingTraktVotes",
+ "columnName": "episode_rating_votes",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingUser",
+ "columnName": "episode_rating_user",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "imdbId",
+ "columnName": "episode_imdbid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastEditedSec",
+ "columnName": "episode_lastedit",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "lastUpdatedSec",
+ "columnName": "episode_lastupdate",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_sg_episode_season_id",
+ "unique": false,
+ "columnNames": [
+ "season_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_sg_episode_season_id` ON `${TABLE_NAME}` (`season_id`)"
+ },
+ {
+ "name": "index_sg_episode_series_id",
+ "unique": false,
+ "columnNames": [
+ "series_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_sg_episode_series_id` ON `${TABLE_NAME}` (`series_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "sg_show",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "series_id"
+ ],
+ "referencedColumns": [
+ "_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "lists",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `list_id` TEXT NOT NULL, `list_name` TEXT NOT NULL, `list_order` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "listId",
+ "columnName": "list_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "list_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "list_order",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_lists_list_id",
+ "unique": true,
+ "columnNames": [
+ "list_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_lists_list_id` ON `${TABLE_NAME}` (`list_id`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "listitems",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `list_item_id` TEXT NOT NULL, `item_ref_id` TEXT NOT NULL, `item_type` INTEGER NOT NULL, `list_id` TEXT, FOREIGN KEY(`list_id`) REFERENCES `lists`(`list_id`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "listItemId",
+ "columnName": "list_item_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "itemRefId",
+ "columnName": "item_ref_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "item_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "listId",
+ "columnName": "list_id",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_listitems_list_item_id",
+ "unique": true,
+ "columnNames": [
+ "list_item_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_listitems_list_item_id` ON `${TABLE_NAME}` (`list_item_id`)"
+ },
+ {
+ "name": "index_listitems_list_id",
+ "unique": false,
+ "columnNames": [
+ "list_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_listitems_list_id` ON `${TABLE_NAME}` (`list_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "lists",
+ "onDelete": "NO ACTION",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "list_id"
+ ],
+ "referencedColumns": [
+ "list_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "movies",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `movies_tmdbid` INTEGER NOT NULL, `movies_imdbid` TEXT, `movies_title` TEXT, `movies_title_noarticle` TEXT, `movies_poster` TEXT, `movies_genres` TEXT, `movies_overview` TEXT, `movies_released` INTEGER, `movies_runtime` INTEGER, `movies_trailer` TEXT, `movies_certification` TEXT, `movies_incollection` INTEGER, `movies_inwatchlist` INTEGER, `movies_plays` INTEGER, `movies_watched` INTEGER, `movies_rating_tmdb` REAL, `movies_rating_votes_tmdb` INTEGER, `movies_rating_trakt` INTEGER, `movies_rating_votes_trakt` INTEGER, `movies_rating_user` INTEGER, `movies_last_updated` INTEGER)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "tmdbId",
+ "columnName": "movies_tmdbid",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "imdbId",
+ "columnName": "movies_imdbid",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "movies_title",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "titleNoArticle",
+ "columnName": "movies_title_noarticle",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "poster",
+ "columnName": "movies_poster",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "genres",
+ "columnName": "movies_genres",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "overview",
+ "columnName": "movies_overview",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "releasedMs",
+ "columnName": "movies_released",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "runtimeMin",
+ "columnName": "movies_runtime",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "trailer",
+ "columnName": "movies_trailer",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "certification",
+ "columnName": "movies_certification",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inCollection",
+ "columnName": "movies_incollection",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "inWatchlist",
+ "columnName": "movies_inwatchlist",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "plays",
+ "columnName": "movies_plays",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "watched",
+ "columnName": "movies_watched",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingTmdb",
+ "columnName": "movies_rating_tmdb",
+ "affinity": "REAL",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingVotesTmdb",
+ "columnName": "movies_rating_votes_tmdb",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingTrakt",
+ "columnName": "movies_rating_trakt",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingVotesTrakt",
+ "columnName": "movies_rating_votes_trakt",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "ratingUser",
+ "columnName": "movies_rating_user",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "lastUpdated",
+ "columnName": "movies_last_updated",
+ "affinity": "INTEGER",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_movies_movies_tmdbid",
+ "unique": true,
+ "columnNames": [
+ "movies_tmdbid"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_movies_movies_tmdbid` ON `${TABLE_NAME}` (`movies_tmdbid`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "activity",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `activity_episode` TEXT NOT NULL, `activity_show` TEXT NOT NULL, `activity_time` INTEGER NOT NULL, `activity_type` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "episodeTvdbOrTmdbId",
+ "columnName": "activity_episode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "showTvdbOrTmdbId",
+ "columnName": "activity_show",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "timestampMs",
+ "columnName": "activity_time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "activity_type",
+ "columnName": "activity_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_activity_activity_episode_activity_type",
+ "unique": true,
+ "columnNames": [
+ "activity_episode",
+ "activity_type"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_activity_activity_episode_activity_type` ON `${TABLE_NAME}` (`activity_episode`, `activity_type`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "jobs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `job_created_at` INTEGER, `job_type` INTEGER, `job_extras` BLOB)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "createdMs",
+ "columnName": "job_created_at",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "job_type",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "extras",
+ "columnName": "job_extras",
+ "affinity": "BLOB",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_jobs_job_created_at",
+ "unique": true,
+ "columnNames": [
+ "job_created_at"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_jobs_job_created_at` ON `${TABLE_NAME}` (`job_created_at`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "sg_watch_provider",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `provider_id` INTEGER NOT NULL, `provider_name` TEXT NOT NULL, `display_priority` INTEGER NOT NULL, `logo_path` TEXT NOT NULL, `type` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `filter_local` INTEGER NOT NULL DEFAULT false)",
+ "fields": [
+ {
+ "fieldPath": "_id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "provider_id",
+ "columnName": "provider_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "provider_name",
+ "columnName": "provider_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "display_priority",
+ "columnName": "display_priority",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "logo_path",
+ "columnName": "logo_path",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "enabled",
+ "columnName": "enabled",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "filter_local",
+ "columnName": "filter_local",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "false"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [
+ {
+ "name": "index_sg_watch_provider_provider_id_type",
+ "unique": true,
+ "columnNames": [
+ "provider_id",
+ "type"
+ ],
+ "orders": [],
+ "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_sg_watch_provider_provider_id_type` ON `${TABLE_NAME}` (`provider_id`, `type`)"
+ },
+ {
+ "name": "index_sg_watch_provider_provider_name",
+ "unique": false,
+ "columnNames": [
+ "provider_name"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_sg_watch_provider_provider_name` ON `${TABLE_NAME}` (`provider_name`)"
+ },
+ {
+ "name": "index_sg_watch_provider_display_priority",
+ "unique": false,
+ "columnNames": [
+ "display_priority"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_sg_watch_provider_display_priority` ON `${TABLE_NAME}` (`display_priority`)"
+ },
+ {
+ "name": "index_sg_watch_provider_enabled",
+ "unique": false,
+ "columnNames": [
+ "enabled"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_sg_watch_provider_enabled` ON `${TABLE_NAME}` (`enabled`)"
+ },
+ {
+ "name": "index_sg_watch_provider_type",
+ "unique": false,
+ "columnNames": [
+ "type"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_sg_watch_provider_type` ON `${TABLE_NAME}` (`type`)"
+ }
+ ],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "sg_watch_provider_show_mappings",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`provider_id` INTEGER NOT NULL, `show_id` INTEGER NOT NULL, PRIMARY KEY(`provider_id`, `show_id`))",
+ "fields": [
+ {
+ "fieldPath": "provider_id",
+ "columnName": "provider_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "show_id",
+ "columnName": "show_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "provider_id",
+ "show_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '37e698b27a2494936bc264752cbf9943')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/battlelancer/seriesguide/provider/DefaultValuesTest.java b/app/src/androidTest/java/com/battlelancer/seriesguide/provider/DefaultValuesTest.java
index c010e8e281..49254919c5 100644
--- a/app/src/androidTest/java/com/battlelancer/seriesguide/provider/DefaultValuesTest.java
+++ b/app/src/androidTest/java/com/battlelancer/seriesguide/provider/DefaultValuesTest.java
@@ -128,6 +128,8 @@ public void showDefaultValuesImport() {
assertThat(show.getCustomReleaseTime()).isNull();
assertThat(show.getCustomReleaseDayOffset()).isNull();
assertThat(show.getCustomReleaseTimeZone()).isNull();
+ assertThat(show.getUserNote()).isNull();
+ assertThat(show.getUserNoteTraktId()).isNull();
}
@Test
diff --git a/app/src/androidTest/java/com/battlelancer/seriesguide/shows/tools/ShowTestHelper.kt b/app/src/androidTest/java/com/battlelancer/seriesguide/shows/tools/ShowTestHelper.kt
index 41ae184c77..1b0c1494d0 100644
--- a/app/src/androidTest/java/com/battlelancer/seriesguide/shows/tools/ShowTestHelper.kt
+++ b/app/src/androidTest/java/com/battlelancer/seriesguide/shows/tools/ShowTestHelper.kt
@@ -41,7 +41,9 @@ object ShowTestHelper {
posterSmall = "poster.jpg",
// set desired language, might not be the content language if fallback used above.
language = LanguageTools.LANGUAGE_EN,
- lastUpdatedMs = System.currentTimeMillis() // now
+ lastUpdatedMs = System.currentTimeMillis(), // now
+ userNote = null,
+ userNoteTraktId = null
)
}
diff --git a/app/src/androidTest/java/com/battlelancer/seriesguide/ui/ShowsActivityTest.java b/app/src/androidTest/java/com/battlelancer/seriesguide/ui/ShowsActivityTest.java
index a16a712ab8..a729833974 100644
--- a/app/src/androidTest/java/com/battlelancer/seriesguide/ui/ShowsActivityTest.java
+++ b/app/src/androidTest/java/com/battlelancer/seriesguide/ui/ShowsActivityTest.java
@@ -64,7 +64,7 @@ public void testAddShowAndSetWatchedThenReturn() {
// Open discover screen
ViewInteraction floatingActionButton = onView(
- allOf(withId(R.id.buttonShowsAdd), isDisplayed()));
+ allOf(withId(R.id.buttonShowsFloating), isDisplayed()));
floatingActionButton.perform(click());
// Added a sleep statement to match the app's execution delay.
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3266a7186b..798b53ddeb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -41,13 +41,12 @@
-
seasons;
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/history/UserEpisodeStreamFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/history/UserEpisodeStreamFragment.kt
index 27276d64c4..9ef3456159 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/history/UserEpisodeStreamFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/history/UserEpisodeStreamFragment.kt
@@ -1,18 +1,17 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2014-2024 Uwe Trottmann
package com.battlelancer.seriesguide.history
import android.os.Bundle
import android.view.View
-import androidx.core.app.ActivityCompat
-import androidx.core.app.ActivityOptionsCompat
import androidx.lifecycle.lifecycleScope
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import com.battlelancer.seriesguide.provider.SgRoomDatabase
import com.battlelancer.seriesguide.shows.episodes.EpisodesActivity
import com.battlelancer.seriesguide.shows.search.discover.AddShowDialogFragment
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import com.uwetrottmann.trakt5.entities.HistoryEntry
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -73,12 +72,7 @@ class UserEpisodeStreamFragment : StreamFragment() {
// Is in database, show details.
val intent =
EpisodesActivity.intentEpisode(episodeIdOrNull, requireContext())
- ActivityCompat.startActivity(
- requireActivity(), intent,
- ActivityOptionsCompat
- .makeScaleUpAnimation(view, 0, 0, view.width, view.height)
- .toBundle()
- )
+ requireActivity().startActivityWithAnimation(intent, view)
} else {
// Offer to add the show if not in database.
AddShowDialogFragment.show(
diff --git a/app/src/main/java/com/battlelancer/seriesguide/history/UserMovieStreamFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/history/UserMovieStreamFragment.kt
index f11b9950e4..1fa360287d 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/history/UserMovieStreamFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/history/UserMovieStreamFragment.kt
@@ -1,15 +1,14 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2014-2024 Uwe Trottmann
package com.battlelancer.seriesguide.history
import android.os.Bundle
import android.view.View
-import androidx.core.app.ActivityCompat
-import androidx.core.app.ActivityOptionsCompat
import androidx.loader.app.LoaderManager
import androidx.loader.content.Loader
import com.battlelancer.seriesguide.movies.details.MovieDetailsActivity
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import com.uwetrottmann.trakt5.entities.HistoryEntry
/**
@@ -46,11 +45,7 @@ class UserMovieStreamFragment : StreamFragment() {
// display movie details
val tmdb = item.movie?.ids?.tmdb ?: return
val i = MovieDetailsActivity.intentMovie(requireContext(), tmdb)
- ActivityCompat.startActivity(
- requireContext(), i, ActivityOptionsCompat
- .makeScaleUpAnimation(view, 0, 0, view.width, view.height)
- .toBundle()
- )
+ requireContext().startActivityWithAnimation(i, view)
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/jobs/TraktEpisodeJob.kt b/app/src/main/java/com/battlelancer/seriesguide/jobs/TraktEpisodeJob.kt
index a0dabc4482..622c444852 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/jobs/TraktEpisodeJob.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/jobs/TraktEpisodeJob.kt
@@ -227,8 +227,7 @@ class TraktEpisodeJob(
}
WatchedEpisode(number, season)
}
- val pageCount = response.headers()["x-pagination-page-count"]?.toIntOrNull()
- ?: 1
+ val pageCount = TraktV2.getPageCount(response) ?: 1
Ok(HistoryPage(episodes, pageCount))
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/lists/AddListDialogFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/lists/AddListDialogFragment.kt
index 16b24eb6d2..7739081503 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/lists/AddListDialogFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/lists/AddListDialogFragment.kt
@@ -1,5 +1,5 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2012-2024 Uwe Trottmann
package com.battlelancer.seriesguide.lists
@@ -12,7 +12,7 @@ import android.widget.TextView
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.fragment.app.FragmentManager
import com.battlelancer.seriesguide.R
-import com.battlelancer.seriesguide.databinding.DialogListManageBinding
+import com.battlelancer.seriesguide.databinding.DialogAddListBinding
import com.battlelancer.seriesguide.provider.SeriesGuideContract
import com.battlelancer.seriesguide.util.safeShow
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@@ -23,10 +23,10 @@ import com.google.android.material.textfield.TextInputLayout
*/
class AddListDialogFragment : AppCompatDialogFragment() {
- private var binding: DialogListManageBinding? = null
+ private var binding: DialogAddListBinding? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val binding = DialogListManageBinding.inflate(layoutInflater)
+ val binding = DialogAddListBinding.inflate(layoutInflater)
this.binding = binding
// title
diff --git a/app/src/main/java/com/battlelancer/seriesguide/lists/DeleteListDialogFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/lists/DeleteListDialogFragment.kt
new file mode 100644
index 0000000000..be178ad110
--- /dev/null
+++ b/app/src/main/java/com/battlelancer/seriesguide/lists/DeleteListDialogFragment.kt
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2024 Uwe Trottmann
+
+package com.battlelancer.seriesguide.lists
+
+import android.app.Dialog
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatDialogFragment
+import androidx.core.os.bundleOf
+import com.battlelancer.seriesguide.R
+import com.battlelancer.seriesguide.provider.SgRoomDatabase
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+
+/**
+ * Dialog to confirm deletion of a list and its items.
+ */
+class DeleteListDialogFragment : AppCompatDialogFragment() {
+
+ private lateinit var listId: String
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ listId = requireArguments().getString(ARG_LIST_ID)
+ ?: throw IllegalArgumentException("$ARG_LIST_ID must be supplied")
+ }
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val listTitle = SgRoomDatabase.getInstance(requireContext()).sgListHelper()
+ .getList(listId)
+ ?.name ?: getString(R.string.unknown)
+
+ // Explicitly make negative button cancel (non-destructive action) as the delete list button
+ // is the negative action in the originating dialog; so accidentally pressing again in the
+ // same region does not do the destructive action.
+ return MaterialAlertDialogBuilder(requireContext())
+ .setTitle(requireContext().getString(R.string.confirm_delete, listTitle))
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ // just dismiss
+ }
+ .setPositiveButton(R.string.list_remove) { _, _ ->
+ ListsTools.deleteList(requireContext(), listId)
+ }
+ .create()
+ }
+
+ companion object {
+
+ private const val ARG_LIST_ID = "list_id"
+
+ fun create(listId: String): DeleteListDialogFragment {
+ return DeleteListDialogFragment().apply {
+ arguments = bundleOf(ARG_LIST_ID to listId)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/battlelancer/seriesguide/lists/ListManageDialogFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/lists/ListManageDialogFragment.kt
index 7080c749a2..5b940e47b1 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/lists/ListManageDialogFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/lists/ListManageDialogFragment.kt
@@ -1,5 +1,5 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2012-2024 Uwe Trottmann
package com.battlelancer.seriesguide.lists
@@ -12,7 +12,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.battlelancer.seriesguide.R
import com.battlelancer.seriesguide.databinding.DialogListManageBinding
-import com.battlelancer.seriesguide.provider.SeriesGuideContract
+import com.battlelancer.seriesguide.provider.SgRoomDatabase
import com.battlelancer.seriesguide.util.safeShow
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
@@ -39,23 +39,29 @@ class ListManageDialogFragment : AppCompatDialogFragment() {
this.binding = binding
// buttons
- binding.buttonNegative.isEnabled = false
- binding.buttonNegative.setText(R.string.list_remove)
- binding.buttonNegative.setOnClickListener {
- // remove list and items
- ListsTools.removeList(requireContext(), listId)
- dismiss()
+ binding.buttonListManageDelete.apply {
+ isEnabled = false
+ setText(R.string.list_remove)
+ setOnClickListener {
+ // ask about removing list
+ DeleteListDialogFragment.create(listId)
+ .safeShow(parentFragmentManager, "confirm-delete-list")
+ dismiss()
+ }
}
- binding.buttonPositive.setText(android.R.string.ok)
- binding.buttonPositive.setOnClickListener {
- val editText = this.binding?.textInputLayoutListManageListName?.editText
- ?: return@setOnClickListener
-
- // update title
- val listName = editText.text.toString().trim()
- ListsTools.renameList(requireContext(), listId, listName)
-
- dismiss()
+ binding.buttonListManageConfirm.apply {
+ setText(R.string.action_save)
+ setOnClickListener {
+ val editText =
+ this@ListManageDialogFragment.binding?.textInputLayoutListManageListName?.editText
+ ?: return@setOnClickListener
+
+ // update title
+ val listName = editText.text.toString().trim()
+ ListsTools.renameList(requireContext(), listId, listName)
+
+ dismiss()
+ }
}
// Delay loading data for views to after this function
@@ -71,26 +77,16 @@ class ListManageDialogFragment : AppCompatDialogFragment() {
}
private fun configureViews() {
+ // Querying on main thread as the queries are very small
+ val listHelper = SgRoomDatabase.getInstance(requireContext()).sgListHelper()
// pre-populate list title
- val list = requireContext().contentResolver
- .query(
- SeriesGuideContract.Lists.buildListUri(listId), arrayOf(
- SeriesGuideContract.Lists.NAME
- ), null, null, null
- )
+ val list = listHelper.getList(listId)
if (list == null) {
// list might have been removed, or query failed
dismiss()
return
}
- if (!list.moveToFirst()) {
- // list not found
- list.close()
- dismiss()
- return
- }
- val listName = list.getString(0)
- list.close()
+ val listName = list.name
val binding = this@ListManageDialogFragment.binding
if (binding == null) {
@@ -104,21 +100,14 @@ class ListManageDialogFragment : AppCompatDialogFragment() {
editTextName.addTextChangedListener(
AddListDialogFragment.ListNameTextWatcher(
requireContext(), textInputLayoutName,
- binding.buttonPositive, listName
+ binding.buttonListManageConfirm, listName
)
)
// do only allow removing if this is NOT the last list
- val lists = requireContext().contentResolver.query(
- SeriesGuideContract.Lists.CONTENT_URI, arrayOf(
- SeriesGuideContract.Lists._ID
- ), null, null, null
- )
- if (lists != null) {
- if (lists.count > 1) {
- binding.buttonNegative.isEnabled = true
- }
- lists.close()
+ val listsCount = listHelper.getListsCount()
+ if (listsCount > 1) {
+ binding.buttonListManageDelete.isEnabled = true
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/lists/ListsReorderDialogFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/lists/ListsReorderDialogFragment.kt
index 5635c053ff..a457ea45a6 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/lists/ListsReorderDialogFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/lists/ListsReorderDialogFragment.kt
@@ -1,8 +1,9 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2015-2024 Uwe Trottmann
package com.battlelancer.seriesguide.lists
+import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
@@ -24,6 +25,8 @@ class ListsReorderDialogFragment : AppCompatDialogFragment() {
private var binding: DialogListsReorderBinding? = null
private lateinit var adapter: ListsAdapter
+ // ClickableViewAccessibility: there is nothing to click
+ @SuppressLint("ClickableViewAccessibility")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogListsReorderBinding.inflate(layoutInflater)
this.binding = binding
diff --git a/app/src/main/java/com/battlelancer/seriesguide/lists/ListsTools.java b/app/src/main/java/com/battlelancer/seriesguide/lists/ListsTools.java
index 996d874036..39ff5d9004 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/lists/ListsTools.java
+++ b/app/src/main/java/com/battlelancer/seriesguide/lists/ListsTools.java
@@ -13,7 +13,7 @@
import com.battlelancer.seriesguide.util.tasks.AddListTask;
import com.battlelancer.seriesguide.util.tasks.ChangeListItemListsTask;
import com.battlelancer.seriesguide.util.tasks.RemoveListItemTask;
-import com.battlelancer.seriesguide.util.tasks.RemoveListTask;
+import com.battlelancer.seriesguide.util.tasks.DeleteListTask;
import com.battlelancer.seriesguide.util.tasks.RenameListTask;
import com.battlelancer.seriesguide.util.tasks.ReorderListsTask;
import com.uwetrottmann.seriesguide.backend.lists.model.SgListItem;
@@ -60,8 +60,8 @@ static void renameList(@NonNull Context context, @NonNull String listId,
AsyncTask.THREAD_POOL_EXECUTOR);
}
- static void removeList(@NonNull Context context, @NonNull String listId) {
- new RemoveListTask(context, listId).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+ static void deleteList(@NonNull Context context, @NonNull String listId) {
+ new DeleteListTask(context, listId).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
static void reorderLists(@NonNull Context context,
diff --git a/app/src/main/java/com/battlelancer/seriesguide/lists/SgListFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/lists/SgListFragment.kt
index 4255e0cf04..4de9345f49 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/lists/SgListFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/lists/SgListFragment.kt
@@ -25,8 +25,8 @@ import com.battlelancer.seriesguide.shows.tools.ShowSync
import com.battlelancer.seriesguide.ui.AutoGridLayoutManager
import com.battlelancer.seriesguide.ui.OverviewActivity
import com.battlelancer.seriesguide.ui.widgets.SgFastScroller
-import com.battlelancer.seriesguide.util.Utils
import com.battlelancer.seriesguide.util.ViewTools
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -112,8 +112,7 @@ class SgListFragment : Fragment() {
private val itemClickListener: SgListItemViewHolder.ItemClickListener =
object : SgListItemViewHolder.ItemClickListener {
override fun onItemClick(anchor: View, item: SgListItemWithDetails) {
- Utils.startActivityWithAnimation(
- requireActivity(),
+ requireActivity().startActivityWithAnimation(
OverviewActivity.intentShow(requireActivity(), item.showId),
anchor
)
diff --git a/app/src/main/java/com/battlelancer/seriesguide/lists/database/SgListHelper.kt b/app/src/main/java/com/battlelancer/seriesguide/lists/database/SgListHelper.kt
index f1e3027bcb..dc8e5c02e4 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/lists/database/SgListHelper.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/lists/database/SgListHelper.kt
@@ -23,12 +23,24 @@ import com.battlelancer.seriesguide.shows.tools.ShowStatus
@Dao
interface SgListHelper {
+ /**
+ * Is null on error or if it does not exist.
+ */
+ @Query("SELECT * FROM lists WHERE list_id = :id")
+ fun getList(id: String): SgList?
+
@Query("SELECT * FROM lists ORDER BY ${Lists.SORT_ORDER_THEN_NAME}")
fun getListsForDisplay(): LiveData>
@Query("SELECT * FROM lists ORDER BY ${Lists.SORT_ORDER_THEN_NAME}")
fun getListsForExport(): List
+ /**
+ * Is 0 on error.
+ */
+ @Query("SELECT COUNT(_id) FROM lists")
+ fun getListsCount(): Int
+
@Query("SELECT * FROM listitems WHERE item_ref_id = :tmdbId AND item_type = ${ListItemTypes.TMDB_SHOW}")
fun getListItemsWithTmdbId(tmdbId: Int): List
diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MovieClickListenerImpl.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MovieClickListenerImpl.kt
index d063a9c236..c2081b0f13 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/movies/MovieClickListenerImpl.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MovieClickListenerImpl.kt
@@ -13,6 +13,7 @@ import com.battlelancer.seriesguide.movies.details.MovieDetailsActivity
import com.battlelancer.seriesguide.movies.tools.MovieTools
import com.battlelancer.seriesguide.provider.SgRoomDatabase
import com.battlelancer.seriesguide.util.Utils
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
open class MovieClickListenerImpl(val context: Context) : MovieClickListener {
@@ -21,7 +22,7 @@ open class MovieClickListenerImpl(val context: Context) : MovieClickListener {
// launch details activity
val intent = MovieDetailsActivity.intentMovie(context, movieTmdbId)
- Utils.startActivityWithAnimation(context, intent, posterView)
+ context.startActivityWithAnimation(intent, posterView)
}
override fun onMoreOptionsClick(movieTmdbId: Int, anchor: View) {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesDiscoverFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesDiscoverFragment.kt
index 2f5e62ba83..f086adbfcd 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesDiscoverFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesDiscoverFragment.kt
@@ -25,8 +25,8 @@ import com.battlelancer.seriesguide.movies.MovieLocalizationDialogFragment.Local
import com.battlelancer.seriesguide.movies.MoviesActivityViewModel.ScrollTabToTopEvent
import com.battlelancer.seriesguide.movies.search.MoviesSearchActivity
import com.battlelancer.seriesguide.ui.AutoGridLayoutManager
-import com.battlelancer.seriesguide.util.Utils
import com.battlelancer.seriesguide.util.ViewTools
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -144,7 +144,7 @@ class MoviesDiscoverFragment : Fragment() {
MoviesDiscoverAdapter.ItemClickListener {
override fun onLinkClick(link: MoviesDiscoverLink, anchor: View) {
MoviesSearchActivity.intentLink(context, link)
- .let { Utils.startActivityWithAnimation(context, it, anchor) }
+ .let { context.startActivityWithAnimation(it, anchor) }
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesHistoryFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesHistoryFragment.kt
index 94f3b2bf20..d1c1710dbd 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesHistoryFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/movies/MoviesHistoryFragment.kt
@@ -29,8 +29,8 @@ import com.battlelancer.seriesguide.shows.history.ShowsHistoryAdapter
import com.battlelancer.seriesguide.shows.history.ShowsHistoryAdapter.Item
import com.battlelancer.seriesguide.shows.history.TraktRecentEpisodeHistoryLoader
import com.battlelancer.seriesguide.traktapi.TraktCredentials
-import com.battlelancer.seriesguide.util.Utils
import com.battlelancer.seriesguide.util.ViewTools
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
/**
* From Trakt, displays recently watched movies of the user and Trakt friends.
@@ -254,9 +254,8 @@ class MoviesHistoryFragment : Fragment() {
// display movie details
val i = MovieDetailsActivity.intentMovie(requireContext(), movieTmdbId)
-
// simple scale up animation as there are no images
- Utils.startActivityWithAnimation(activity, i, view)
+ requireActivity().startActivityWithAnimation(i, view)
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/movies/details/MovieDetailsFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/movies/details/MovieDetailsFragment.kt
index 285649397c..f62fa44e16 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/movies/details/MovieDetailsFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/movies/details/MovieDetailsFragment.kt
@@ -70,6 +70,7 @@ import com.battlelancer.seriesguide.util.Utils
import com.battlelancer.seriesguide.util.ViewTools
import com.battlelancer.seriesguide.util.WebTools
import com.battlelancer.seriesguide.util.copyTextToClipboardOnLongClick
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import com.uwetrottmann.androidutils.AndroidUtils
@@ -135,6 +136,13 @@ class MovieDetailsFragment : Fragment(), MovieActionsContract {
}
isEnabled = false
}
+ // release dates button
+ buttonMovieReleaseDates.setOnClickListener {
+ WebTools.openInCustomTab(
+ requireContext(),
+ TmdbTools.buildMovieReleaseDatesUrl(tmdbId)
+ )
+ }
// similar movies button
buttonMovieSimilar.setOnClickListener {
movieDetails?.tmdbMovie()
@@ -469,7 +477,7 @@ class MovieDetailsFragment : Fragment(), MovieActionsContract {
val tmdbId = tmdbId
if (tmdbId > 0) {
val i = TraktCommentsActivity.intentMovie(requireContext(), movieTitle, tmdbId)
- Utils.startActivityWithAnimation(activity, i, v)
+ requireActivity().startActivityWithAnimation(i, v)
}
}
}
@@ -600,7 +608,7 @@ class MovieDetailsFragment : Fragment(), MovieActionsContract {
smallImageUrl,
largeImageUrl
)
- Utils.startActivityWithAnimation(activity, intent, view)
+ requireActivity().startActivityWithAnimation(intent, view)
}
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/people/PeopleListHelper.kt b/app/src/main/java/com/battlelancer/seriesguide/people/PeopleListHelper.kt
index 8e6358e5b1..0497bfb083 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/people/PeopleListHelper.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/people/PeopleListHelper.kt
@@ -16,7 +16,7 @@ import com.battlelancer.seriesguide.tmdbapi.TmdbTools
import com.battlelancer.seriesguide.util.CircleTransformation
import com.battlelancer.seriesguide.util.ImageTools
import com.battlelancer.seriesguide.util.ThemeUtils
-import com.battlelancer.seriesguide.util.Utils
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import timber.log.Timber
/**
@@ -230,7 +230,7 @@ class PeopleListHelper {
// showing a specific person
i.putExtra(PersonFragment.ARG_PERSON_TMDB_ID, personTmdbId)
}
- Utils.startActivityWithAnimation(context, i, v)
+ context.startActivityWithAnimation(i, v)
}
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/preferences/NotificationThresholdDialogFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/preferences/NotificationThresholdDialogFragment.kt
index 82fef8dc33..0048e85651 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/preferences/NotificationThresholdDialogFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/preferences/NotificationThresholdDialogFragment.kt
@@ -1,8 +1,9 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2017-2024 Uwe Trottmann
package com.battlelancer.seriesguide.preferences
+import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.content.res.Resources
@@ -29,6 +30,7 @@ class NotificationThresholdDialogFragment : AppCompatDialogFragment() {
private var binding: DialogNotificationThresholdBinding? = null
private var value = 0
+ @SuppressLint("SetTextI18n")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogNotificationThresholdBinding.inflate(layoutInflater)
this.binding = binding
@@ -52,6 +54,8 @@ class NotificationThresholdDialogFragment : AppCompatDialogFragment() {
value = minutes
binding.radioGroupThreshold.check(R.id.radioButtonThresholdMinutes)
}
+ // Suppress SetTextI18n: to accept Arabic numbers, would need to add custom text parsing
+ // and remove the inputType number restriction.
binding.editTextThresholdValue.setText(value.toString())
// radio buttons are updated by text watcher
diff --git a/app/src/main/java/com/battlelancer/seriesguide/preferences/TimeOffsetDialogFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/preferences/TimeOffsetDialogFragment.kt
index e3e20c5283..7c4bb188f5 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/preferences/TimeOffsetDialogFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/preferences/TimeOffsetDialogFragment.kt
@@ -33,6 +33,7 @@ class TimeOffsetDialogFragment : AppCompatDialogFragment() {
private var hours = 0
+ @SuppressLint("SetTextI18n")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogTimeOffsetBinding.inflate(layoutInflater)
this.binding = binding
@@ -42,6 +43,8 @@ class TimeOffsetDialogFragment : AppCompatDialogFragment() {
binding.editTextOffsetValue.addTextChangedListener(textWatcher)
val hours = getShowsTimeOffset(requireContext())
+ // Suppress SetTextI18n: to accept Arabic numbers, would need to add custom text parsing
+ // and remove the inputType numberSigned restriction.
binding.editTextOffsetValue.setText(hours.toString())
// text views are updated by text watcher
diff --git a/app/src/main/java/com/battlelancer/seriesguide/provider/SeriesGuideContract.java b/app/src/main/java/com/battlelancer/seriesguide/provider/SeriesGuideContract.java
index 4dfeaf1013..0eb54f97d1 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/provider/SeriesGuideContract.java
+++ b/app/src/main/java/com/battlelancer/seriesguide/provider/SeriesGuideContract.java
@@ -721,6 +721,16 @@ public interface SgShow2Columns extends BaseColumns {
*/
String UNWATCHED_COUNT = "series_unwatched_count";
+ /**
+ * See {@link SgShow2#getUserNote()}.
+ */
+ String USER_NOTE = "series_user_note";
+
+ /**
+ * See {@link SgShow2#getUserNoteTraktId()}.
+ */
+ String USER_NOTE_TRAKT_ID = "series_user_note_trakt_id";
+
String SELECTION_FAVORITES = FAVORITE + "=1";
String SELECTION_NOT_FAVORITES = FAVORITE + "=0";
String SELECTION_HIDDEN = HIDDEN + "=1";
diff --git a/app/src/main/java/com/battlelancer/seriesguide/provider/SgRoomDatabase.kt b/app/src/main/java/com/battlelancer/seriesguide/provider/SgRoomDatabase.kt
index ecffd9a3dc..8b3cd8b033 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/provider/SgRoomDatabase.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/provider/SgRoomDatabase.kt
@@ -67,6 +67,10 @@ import timber.log.Timber
AutoMigration(
from = SgRoomDatabase.VERSION_52_WATCH_PROVIDER_FILTERS,
to = SgRoomDatabase.VERSION_53_SHOW_TMDB_RATINGS
+ ),
+ AutoMigration(
+ from = SgRoomDatabase.VERSION_53_SHOW_TMDB_RATINGS,
+ to = SgRoomDatabase.VERSION_54_SHOW_NOTES
)
]
)
@@ -139,7 +143,13 @@ abstract class SgRoomDatabase : RoomDatabase() {
* - Add [SgEpisode2.ratingTmdb] and [SgEpisode2.ratingTmdbVotes].
*/
const val VERSION_53_SHOW_TMDB_RATINGS = 53
- const val VERSION = VERSION_53_SHOW_TMDB_RATINGS
+
+ /**
+ * - Add [SgShow2.userNote] and [SgShow2.userNoteTraktId].
+ */
+ const val VERSION_54_SHOW_NOTES = 54
+
+ const val VERSION = VERSION_54_SHOW_NOTES
@Volatile
private var instance: SgRoomDatabase? = null
diff --git a/app/src/main/java/com/battlelancer/seriesguide/settings/TmdbSettings.kt b/app/src/main/java/com/battlelancer/seriesguide/settings/TmdbSettings.kt
index 8af3c9741b..7766a4defb 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/settings/TmdbSettings.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/settings/TmdbSettings.kt
@@ -21,7 +21,21 @@ object TmdbSettings {
private const val KEY_TMDB_BASE_URL = "com.battlelancer.seriesguide.tmdb.baseurl"
const val POSTER_SIZE_SPEC_W154 = "w154"
const val POSTER_SIZE_SPEC_W342 = "w342"
- private const val STILL_SIZE_SPEC_W300 = "w300"
+
+ /**
+ * As of 2024-11:
+ *
+ * - w300 (300 × 169 px) JPEG is around 9-16 kB
+ * - w780 (780 × 439 px) JPEG is around 30-70 kB
+ * - w1280 (1280 × 720 px) JPEG is around 60-140 KB
+ *
+ * Samples:
+ *
+ * - https://image.tmdb.org/t/p/original/8Tvnx22rzhwArofFPhmcfaBvgjN.jpg (smallest)
+ * - https://image.tmdb.org/t/p/original/j4WEC9Jh4AyXF8ynpX3pz633tse.jpg
+ * - https://image.tmdb.org/t/p/original/5Bh7EE3p6OOS0NzH22AE0N7DYO8.jpg (largest)
+ */
+ const val BACKDROP_SMALL_SIZE_SPEC = "w780"
private const val IMAGE_SIZE_SPEC_ORIGINAL = "original"
const val DEFAULT_BASE_URL = "https://image.tmdb.org/t/p/"
@@ -69,7 +83,7 @@ object TmdbSettings {
}
}
- fun getStillUrl(context: Context, path: String): String {
- return getImageBaseUrl(context) + STILL_SIZE_SPEC_W300 + path
+ fun buildBackdropUrl(context: Context, path: String): String {
+ return "${getImageBaseUrl(context)}$BACKDROP_SMALL_SIZE_SPEC$path"
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsActivityImpl.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsActivityImpl.kt
index 4520f96a7b..8fa2395518 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsActivityImpl.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsActivityImpl.kt
@@ -5,7 +5,6 @@ package com.battlelancer.seriesguide.shows
import android.content.Intent
import android.os.Bundle
-import android.view.KeyEvent
import android.view.View
import androidx.activity.viewModels
import androidx.lifecycle.ViewModelProvider
@@ -188,9 +187,9 @@ open class ShowsActivityImpl : BaseTopActivity() {
}
private fun setupViews() {
- // setup floating action button for adding shows
- val buttonAddShow = findViewById(R.id.buttonShowsAdd)
- buttonAddShow.setOnClickListener {
+ // setup floating action button
+ val floatingActionButton = findViewById(R.id.buttonShowsFloating)
+ floatingActionButton.setOnClickListener {
startActivity(ShowsDiscoverPagingActivity.intentSearch(this))
}
@@ -205,7 +204,7 @@ open class ShowsActivityImpl : BaseTopActivity() {
tabs.setOnPageChangeListener(
ShowsPageChangeListener(
findViewById(R.id.sgAppBarLayout),
- buttonAddShow,
+ floatingActionButton,
viewModel
)
)
@@ -326,11 +325,6 @@ open class ShowsActivityImpl : BaseTopActivity() {
ShowsSettings.saveLastShowsTabPosition(this, viewPager.currentItem)
}
- override fun onKeyLongPress(keyCode: Int, event: KeyEvent): Boolean {
- // prevent navigating to top activity as this is the top activity
- return keyCode == KeyEvent.KEYCODE_BACK
- }
-
override val snackbarParentView: View
get() = findViewById(R.id.coordinatorLayoutShows)
@@ -363,7 +357,6 @@ open class ShowsActivityImpl : BaseTopActivity() {
}
appBarLayout.liftOnScrollTargetViewId = liftOnScrollTarget
- // only display add show button on Shows tab
if (position == Tab.DISCOVER.index) {
floatingActionButton.show()
} else {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsDistillationFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsDistillationFragment.kt
index 771deb5760..bad923775c 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsDistillationFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsDistillationFragment.kt
@@ -33,7 +33,6 @@ import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
class ShowsDistillationFragment : AppCompatDialogFragment() {
private val model: ShowsDistillationViewModel by viewModels()
- private var binding: DialogShowsDistillationBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -178,11 +177,6 @@ class ShowsDistillationFragment : AppCompatDialogFragment() {
}
- override fun onDestroyView() {
- super.onDestroyView()
- binding = null
- }
-
companion object {
private const val TAG = "shows-distillation-dialog"
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsFragment.kt
index 93c786619f..c09623edd8 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/ShowsFragment.kt
@@ -20,8 +20,6 @@ import android.view.ViewGroup
import android.widget.Button
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu
-import androidx.core.app.ActivityCompat
-import androidx.core.app.ActivityOptionsCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
@@ -48,6 +46,7 @@ import com.battlelancer.seriesguide.ui.SearchActivity
import com.battlelancer.seriesguide.ui.menus.ManualSyncMenu
import com.battlelancer.seriesguide.ui.widgets.SgFastScroller
import com.battlelancer.seriesguide.util.ViewTools
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import com.google.android.material.snackbar.Snackbar
import com.uwetrottmann.androidutils.AndroidUtils
import kotlinx.coroutines.Job
@@ -254,13 +253,7 @@ class ShowsFragment : Fragment() {
override fun onItemClick(anchor: View, showRowId: Long) {
// display overview for this show
val intent = intentShow(requireContext(), showRowId)
- ActivityCompat.startActivity(
- requireContext(), intent,
- ActivityOptionsCompat.makeScaleUpAnimation(
- anchor, 0, 0, anchor.width,
- anchor.height
- ).toBundle()
- )
+ requireContext().startActivityWithAnimation(intent, anchor)
}
override fun onMoreOptionsClick(anchor: View, show: ShowItem) {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/WatchProviderFilterView.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/WatchProviderFilterView.kt
index 5fb0fd2f55..dca96a2ca0 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/WatchProviderFilterView.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/WatchProviderFilterView.kt
@@ -4,6 +4,7 @@
package com.battlelancer.seriesguide.shows
import android.content.res.Configuration
+import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -21,6 +22,7 @@ import androidx.compose.material3.Checkbox
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
+import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Surface
@@ -29,6 +31,7 @@ import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@@ -84,16 +87,25 @@ fun WatchProviderList(
}
HorizontalDivider()
Row {
+ // Disabling makes it easier to see if any of the shown provider filters is enabled,
+ // however, it prevents re-setting not shown providers. Which should not matter as
+ // only shown ones are considered for filtering (see
+ // SgWatchProviderHelper.filterLocalWatchProviders).
+ val isResetEnabled = watchProviders.find { it.filter_local } != null
Row(
modifier = Modifier
.weight(1f)
.clickable(
+ enabled = isResetEnabled,
role = Role.Button,
onClick = onProviderIncludeAny
)
.padding(16.dp)
) {
- Text(stringResource(id = R.string.action_reset))
+ TextWithDisabledState(
+ id = R.string.action_reset,
+ enabled = isResetEnabled
+ )
}
val descriptionStreamSettingsButton =
stringResource(id = R.string.action_stream_settings)
@@ -117,6 +129,23 @@ fun WatchProviderList(
}
}
+@Composable
+fun TextWithDisabledState(@StringRes id: Int, enabled: Boolean) {
+ // https://developer.android.com/develop/ui/compose/designsystems/material2-material3#emphasis-and
+ // https://developer.android.com/develop/ui/compose/designsystems/material3#emphasis
+ CompositionLocalProvider(
+ LocalContentColor.provides(
+ if (enabled) {
+ MaterialTheme.colorScheme.onSurface
+ } else {
+ MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
+ }
+ )
+ ) {
+ Text(stringResource(id = id))
+ }
+}
+
@Composable
fun WatchProviderFilterItem(
item: SgWatchProvider,
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/calendar/CalendarFragment2.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/calendar/CalendarFragment2.kt
index 11d5a25402..e59cde6fbf 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/calendar/CalendarFragment2.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/calendar/CalendarFragment2.kt
@@ -37,7 +37,7 @@ import com.battlelancer.seriesguide.ui.AutoGridLayoutManager
import com.battlelancer.seriesguide.ui.SearchActivity
import com.battlelancer.seriesguide.ui.menus.ManualSyncMenu
import com.battlelancer.seriesguide.ui.widgets.SgFastScroller
-import com.battlelancer.seriesguide.util.Utils
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.conflate
@@ -231,7 +231,7 @@ abstract class CalendarFragment2 : Fragment() {
CalendarAdapter2.ItemClickListener {
override fun onItemClick(episodeId: Long) {
val intent = EpisodesActivity.intentEpisode(episodeId, requireContext())
- Utils.startActivityWithAnimation(activity, intent, view)
+ requireActivity().startActivityWithAnimation(intent, view!!)
}
override fun onMoreOptionsClick(anchor: View, episode: SgEpisode2WithShow) {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/database/SgShow2.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/database/SgShow2.kt
index f39bda2695..49cf02860f 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/database/SgShow2.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/database/SgShow2.kt
@@ -49,8 +49,11 @@ import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.TRAKT_ID
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.TVDB_ID
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.UNWATCHED_COUNT
+import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.USER_NOTE
+import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns.USER_NOTE_TRAKT_ID
import com.battlelancer.seriesguide.provider.SeriesGuideContract.SgShow2Columns._ID
import com.battlelancer.seriesguide.provider.SgRoomDatabase
+import com.battlelancer.seriesguide.shows.database.SgShow2.Companion.MAX_USER_NOTE_LENGTH
import com.battlelancer.seriesguide.shows.tools.NextEpisodeUpdater
import com.battlelancer.seriesguide.shows.tools.ShowStatus
import com.battlelancer.seriesguide.util.TimeTools
@@ -163,6 +166,22 @@ data class SgShow2(
@ColumnInfo(name = CUSTOM_RELEASE_TIME) var customReleaseTime: Int?,
@ColumnInfo(name = CUSTOM_RELEASE_DAY_OFFSET) var customReleaseDayOffset: Int?,
@ColumnInfo(name = CUSTOM_RELEASE_TIME_ZONE) var customReleaseTimeZone: String?,
+ /**
+ * A user editable text note for this show.
+ *
+ * Default `null`. Should be at most [MAX_USER_NOTE_LENGTH].
+ *
+ * Added with [SgRoomDatabase.VERSION_54_SHOW_NOTES].
+ */
+ @ColumnInfo(name = USER_NOTE) var userNote: String?,
+ /**
+ * Trakt ID for [userNote].
+ *
+ * Default `null`.
+ *
+ * Added with [SgRoomDatabase.VERSION_54_SHOW_NOTES].
+ */
+ @ColumnInfo(name = USER_NOTE_TRAKT_ID) val userNoteTraktId: Long?
) {
val releaseTimeOrDefault: Int
get() = releaseTime ?: -1
@@ -179,6 +198,12 @@ data class SgShow2(
val statusOrUnknown: Int
get() = status ?: ShowStatus.UNKNOWN
+ /**
+ * Maps blank or null to empty string.
+ */
+ val userNoteOrEmpty: String
+ get() = userNote?.ifBlank { "" } ?: ""
+
companion object {
/**
* Used if the number of remaining episodes to watch for a show is not (yet) known.
@@ -197,5 +222,11 @@ data class SgShow2(
* Maximum absolute (so positive or negative) value of the [customReleaseDayOffset].
*/
const val MAX_CUSTOM_DAY_OFFSET = 28
+
+ /**
+ * Max note length to support Trakt and Hexagon
+ * (Trakt supports up to 500 characters, Hexagon up to 2000).
+ */
+ const val MAX_USER_NOTE_LENGTH = 500
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/database/SgShow2Helper.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/database/SgShow2Helper.kt
index bd03618c24..50ccebd596 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/database/SgShow2Helper.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/database/SgShow2Helper.kt
@@ -145,6 +145,24 @@ interface SgShow2Helper {
@Query("UPDATE sg_show SET series_tmdb_id = :tmdbId WHERE _id = :id")
fun updateTmdbId(id: Long, tmdbId: Int): Int
+ @Query("SELECT _id FROM sg_show WHERE series_user_note IS NOT NULL AND series_user_note != ''")
+ fun getShowIdsWithNotes(): MutableList
+
+ @Query("SELECT _id, series_tmdb_id, series_user_note, series_user_note_trakt_id FROM sg_show WHERE _id = :id")
+ fun getShowWithNote(id: Long): SgShow2WithNote?
+
+ @Query("UPDATE sg_show SET series_user_note = :note, series_user_note_trakt_id = :traktId WHERE _id = :id")
+ fun updateUserNote(id: Long, note: String, traktId: Long?)
+
+ data class NoteUpdate(val text: String, val traktId: Long?)
+
+ @Transaction
+ fun updateUserNotes(notesById: Map) {
+ for (entry in notesById) {
+ updateUserNote(entry.key, entry.value.text, entry.value.traktId)
+ }
+ }
+
@Query("DELETE FROM sg_show")
fun deleteAllShows()
@@ -163,10 +181,10 @@ interface SgShow2Helper {
@Query("UPDATE sg_show SET series_syncenabled = 1 WHERE _id = :id")
fun setHexagonMergeCompleted(id: Long)
- @Query("SELECT _id, series_tmdb_id, series_language, series_favorite, series_hidden, series_notify, series_custom_release_time, series_custom_day_offset, series_custom_timezone, series_lastupdate FROM sg_show WHERE _id = :id")
+ @Query("SELECT _id, series_tmdb_id, series_language, series_favorite, series_hidden, series_notify, series_custom_release_time, series_custom_day_offset, series_custom_timezone, series_lastupdate, series_user_note FROM sg_show WHERE _id = :id")
fun getForCloudUpdate(id: Long): SgShow2CloudUpdate?
- @Query("SELECT _id, series_tmdb_id, series_language, series_favorite, series_hidden, series_notify, series_custom_release_time, series_custom_day_offset, series_custom_timezone, series_lastupdate FROM sg_show")
+ @Query("SELECT _id, series_tmdb_id, series_language, series_favorite, series_hidden, series_notify, series_custom_release_time, series_custom_day_offset, series_custom_timezone, series_lastupdate, series_user_note FROM sg_show")
fun getForCloudUpdate(): List
@Update(entity = SgShow2::class)
@@ -365,7 +383,8 @@ data class SgShow2CloudUpdate(
@ColumnInfo(name = SgShow2Columns.CUSTOM_RELEASE_TIME) var customReleaseTime: Int?,
@ColumnInfo(name = SgShow2Columns.CUSTOM_RELEASE_DAY_OFFSET) var customReleaseDayOffset: Int?,
@ColumnInfo(name = SgShow2Columns.CUSTOM_RELEASE_TIME_ZONE) var customReleaseTimeZone: String?,
- @ColumnInfo(name = SgShow2Columns.LASTUPDATED) var lastUpdatedMs: Long
+ @ColumnInfo(name = SgShow2Columns.LASTUPDATED) var lastUpdatedMs: Long,
+ @ColumnInfo(name = SgShow2Columns.USER_NOTE) var userNote: String?,
)
data class ShowLastWatchedInfo(
@@ -373,3 +392,10 @@ data class ShowLastWatchedInfo(
val episodeSeason: Int,
val episodeNumber: Int
)
+
+data class SgShow2WithNote(
+ @ColumnInfo(name = SgShow2Columns._ID) val id: Long,
+ @ColumnInfo(name = SgShow2Columns.TMDB_ID) val tmdbId: Int?,
+ @ColumnInfo(name = SgShow2Columns.USER_NOTE) var userNote: String?,
+ @ColumnInfo(name = SgShow2Columns.USER_NOTE_TRAKT_ID) val userNoteTraktId: Long?
+)
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/episodes/EpisodeDetailsFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/episodes/EpisodeDetailsFragment.kt
index c90acb61c9..171f98b77f 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/episodes/EpisodeDetailsFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/episodes/EpisodeDetailsFragment.kt
@@ -52,7 +52,6 @@ import com.battlelancer.seriesguide.ui.BaseMessageActivity.ServiceActiveEvent
import com.battlelancer.seriesguide.ui.BaseMessageActivity.ServiceCompletedEvent
import com.battlelancer.seriesguide.ui.FullscreenImageActivity.Companion.intent
import com.battlelancer.seriesguide.util.ImageTools
-import com.battlelancer.seriesguide.util.ImageTools.tmdbOrTvdbStillUrl
import com.battlelancer.seriesguide.util.LanguageTools
import com.battlelancer.seriesguide.util.RatingsTools.initialize
import com.battlelancer.seriesguide.util.RatingsTools.setLink
@@ -66,6 +65,7 @@ import com.battlelancer.seriesguide.util.Utils
import com.battlelancer.seriesguide.util.ViewTools
import com.battlelancer.seriesguide.util.copyTextToClipboardOnLongClick
import com.battlelancer.seriesguide.util.safeShow
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import kotlinx.coroutines.Job
@@ -448,13 +448,13 @@ class EpisodeDetailsFragment : Fragment(), EpisodeActionsContract {
// episode image
val imagePath = episode.image
- binding.containerImage.setOnClickListener { v: View? ->
+ binding.containerImage.setOnClickListener { v: View ->
val intent = intent(
requireContext(),
- tmdbOrTvdbStillUrl(imagePath, requireContext(), false),
- tmdbOrTvdbStillUrl(imagePath, requireContext(), true)
+ ImageTools.buildEpisodeImageUrl(imagePath, requireContext()),
+ ImageTools.buildEpisodeImageUrl(imagePath, requireContext(), originalSize = true)
)
- Utils.startActivityWithAnimation(requireActivity(), intent, v)
+ requireActivity().startActivityWithAnimation(intent, v)
}
loadImage(imagePath, hideDetails)
@@ -595,12 +595,12 @@ class EpisodeDetailsFragment : Fragment(), EpisodeActionsContract {
}
// Trakt comments
- bindingButtons.buttonEpisodeComments.setOnClickListener { v: View? ->
+ bindingButtons.buttonEpisodeComments.setOnClickListener { v: View ->
val episodeId = episodeId
if (episodeId > 0) {
val intent =
TraktCommentsActivity.intentEpisode(requireContext(), episodeTitle, episodeId)
- Utils.startActivityWithAnimation(requireActivity(), intent, v)
+ requireActivity().startActivityWithAnimation(intent, v)
}
}
}
@@ -685,7 +685,7 @@ class EpisodeDetailsFragment : Fragment(), EpisodeActionsContract {
binding.containerImage.visibility = View.VISIBLE
ImageTools.loadWithPicasso(
requireContext(),
- tmdbOrTvdbStillUrl(imagePath, requireContext(), false)
+ ImageTools.buildEpisodeImageUrl(imagePath, requireContext())
)
.error(R.drawable.ic_photo_gray_24dp)
.into(
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/episodes/EpisodesFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/episodes/EpisodesFragment.kt
index 01ec71a92b..1a84a182d0 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/episodes/EpisodesFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/episodes/EpisodesFragment.kt
@@ -318,7 +318,7 @@ class EpisodesFragment : Fragment() {
R.array.epsortingData,
EpisodesSettings.getEpisodeSortOrder(requireActivity()).index(),
EpisodesSettings.KEY_EPISODE_SORT_ORDER,
- R.string.pref_episodesorting,
+ R.string.sort,
"episodeSortOrderDialog"
)
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/history/ShowsHistoryFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/history/ShowsHistoryFragment.kt
index 0a0cce89a4..5f4e967488 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/history/ShowsHistoryFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/history/ShowsHistoryFragment.kt
@@ -12,8 +12,6 @@ import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
-import androidx.core.app.ActivityCompat
-import androidx.core.app.ActivityOptionsCompat
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
@@ -36,6 +34,7 @@ import com.battlelancer.seriesguide.traktapi.TraktCredentials
import com.battlelancer.seriesguide.ui.BaseMessageActivity.ServiceCompletedEvent
import com.battlelancer.seriesguide.ui.SearchActivity
import com.battlelancer.seriesguide.util.ViewTools
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
@@ -266,13 +265,7 @@ class ShowsHistoryFragment : Fragment() {
*/
private fun showDetails(view: View, episodeId: Long) {
val intent = EpisodesActivity.intentEpisode(episodeId, requireContext())
-
- ActivityCompat.startActivity(
- requireContext(), intent,
- ActivityOptionsCompat
- .makeScaleUpAnimation(view, 0, 0, view.width, view.height)
- .toBundle()
- )
+ requireContext().startActivityWithAnimation(intent, view)
}
private fun showError(errorText: String?) {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/EditNoteDialog.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/EditNoteDialog.kt
new file mode 100644
index 0000000000..ce5a93e6f9
--- /dev/null
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/EditNoteDialog.kt
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2024 Uwe Trottmann
+
+package com.battlelancer.seriesguide.shows.overview
+
+import android.app.Dialog
+import android.os.Bundle
+import android.text.Editable
+import androidx.appcompat.app.AppCompatDialogFragment
+import androidx.core.os.bundleOf
+import androidx.core.widget.doAfterTextChanged
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.battlelancer.seriesguide.R
+import com.battlelancer.seriesguide.databinding.DialogEditNoteBinding
+import com.battlelancer.seriesguide.shows.database.SgShow2
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import kotlinx.coroutines.launch
+import timber.log.Timber
+
+/**
+ * Edits a note of a show, enforcing a maximum length, allows to save or discard changes.
+ */
+class EditNoteDialog() : AppCompatDialogFragment() {
+
+ constructor(showId: Long) : this() {
+ arguments = bundleOf(
+ ARG_SHOW_ID to showId
+ )
+ }
+
+ private var binding: DialogEditNoteBinding? = null
+ private val model: EditNoteDialogViewModel by viewModels(
+ extrasProducer = {
+ EditNoteDialogViewModel.creationExtras(
+ defaultViewModelCreationExtras,
+ requireArguments().getLong(ARG_SHOW_ID)
+ )
+ },
+ factoryProducer = { EditNoteDialogViewModel.Factory }
+ )
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val binding = DialogEditNoteBinding.inflate(layoutInflater)
+ .also { this.binding = it }
+
+ // Text field
+ binding.textFieldEditNote.counterMaxLength = SgShow2.MAX_USER_NOTE_LENGTH
+ val editText = binding.textFieldEditNote.editText!!
+ // Disable save button if text is too long to save
+ editText.doAfterTextChanged { text ->
+ setSaveEnabled(model.uiState.value.isEditingEnabled, text.textHasNoError())
+ }
+
+ // Buttons
+ // Can not use dialog buttons as they dismiss the dialog right away,
+ // but need to keep it visible if saving fails.
+ binding.buttonPositive.apply {
+ setText(R.string.action_save)
+ setOnClickListener {
+ model.updateNote(editText.text?.toString())
+ model.saveNote()
+ }
+ }
+ binding.buttonNegative.apply {
+ setText(android.R.string.cancel)
+ setOnClickListener { dismiss() }
+ }
+
+ // UI state
+ lifecycleScope.launch {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ model.uiState.collect { state ->
+ Timber.d("Display note")
+ editText.setText(state.noteText)
+ setViewsEnabled(state.isEditingEnabled, editText.text.textHasNoError())
+ if (state.isNoteSaved) {
+ dismiss()
+ }
+ }
+ }
+ }
+
+ return MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.action_edit_note)
+ .setView(binding.root)
+ .create()
+ }
+
+ private fun setViewsEnabled(isEditingEnabled: Boolean, hasNoError: Boolean) {
+ binding?.apply {
+ textFieldEditNote.isEnabled = isEditingEnabled
+ setSaveEnabled(isEditingEnabled, hasNoError)
+ }
+ }
+
+ private fun Editable?.textHasNoError(): Boolean {
+ val textLength = this?.length ?: 0
+ return textLength <= SgShow2.MAX_USER_NOTE_LENGTH
+ }
+
+ private fun setSaveEnabled(isEditingEnabled: Boolean, hasNoError: Boolean) {
+ binding?.buttonPositive?.isEnabled = isEditingEnabled && hasNoError
+ }
+
+ override fun onPause() {
+ super.onPause()
+ // Note: can not update using TextWatcher due to infinite loop
+ model.updateNote(binding?.textFieldEditNote?.editText?.text?.toString())
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ binding = null
+ }
+
+ companion object {
+ private const val ARG_SHOW_ID = "showId"
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/EditNoteDialogViewModel.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/EditNoteDialogViewModel.kt
new file mode 100644
index 0000000000..4aa07d306d
--- /dev/null
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/EditNoteDialogViewModel.kt
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2024 Uwe Trottmann
+
+package com.battlelancer.seriesguide.shows.overview
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
+import androidx.lifecycle.viewModelScope
+import androidx.lifecycle.viewmodel.CreationExtras
+import androidx.lifecycle.viewmodel.MutableCreationExtras
+import androidx.lifecycle.viewmodel.initializer
+import androidx.lifecycle.viewmodel.viewModelFactory
+import com.battlelancer.seriesguide.SgApp
+import com.battlelancer.seriesguide.provider.SgRoomDatabase
+import com.battlelancer.seriesguide.shows.tools.ShowTools2
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
+
+class EditNoteDialogViewModel(application: Application, private val showId: Long) :
+ AndroidViewModel(application) {
+
+ data class EditNoteDialogUiState(
+ val noteText: String = "",
+ val noteTraktId: Long? = null,
+ val isEditingEnabled: Boolean = false,
+ val isNoteSaved: Boolean = false
+ )
+
+ val uiState = MutableStateFlow(EditNoteDialogUiState())
+
+ init {
+ viewModelScope.launch(Dispatchers.IO) {
+ val show = SgRoomDatabase.getInstance(application)
+ .sgShow2Helper()
+ .getShow(showId)
+ if (show != null) {
+ uiState.update {
+ it.copy(
+ noteText = show.userNoteOrEmpty,
+ noteTraktId = show.userNoteTraktId,
+ isEditingEnabled = true
+ )
+ }
+ }
+ }
+ }
+
+ fun updateNote(text: String?) {
+ uiState.update {
+ it.copy(noteText = text ?: "")
+ }
+ }
+
+ /**
+ * Tries to save the note, see [ShowTools2.storeUserNote].
+ *
+ * Updates UI state depending on success.
+ */
+ fun saveNote() {
+ uiState.update {
+ it.copy(isEditingEnabled = false)
+ }
+ val noteDraft = uiState.value.noteText
+ val noteTraktId = uiState.value.noteTraktId
+ viewModelScope.launch {
+ val result = SgApp.getServicesComponent(getApplication()).showTools()
+ .storeUserNote(showId, noteDraft, noteTraktId)
+ uiState.update {
+ if (result != null) {
+ it.copy(
+ noteText = result.text,
+ noteTraktId = result.traktId,
+ isEditingEnabled = true,
+ isNoteSaved = true
+ )
+ } else {
+ // Failed, re-enable buttons
+ it.copy(
+ isEditingEnabled = true
+ )
+ }
+ }
+ }
+ }
+
+ companion object {
+
+ private val KEY_SHOW_ID = object : CreationExtras.Key {}
+
+ val Factory = viewModelFactory {
+ initializer {
+ val application = this[APPLICATION_KEY]!!
+ val showId = this[KEY_SHOW_ID]!!
+ EditNoteDialogViewModel(application, showId)
+ }
+ }
+
+ fun creationExtras(defaultExtras: CreationExtras, showId: Long) =
+ MutableCreationExtras(defaultExtras).apply {
+ set(KEY_SHOW_ID, showId)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/OverviewFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/OverviewFragment.kt
index 91245b6a6e..361582d073 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/OverviewFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/OverviewFragment.kt
@@ -60,7 +60,6 @@ import com.battlelancer.seriesguide.traktapi.TraktTools
import com.battlelancer.seriesguide.ui.BaseMessageActivity.ServiceActiveEvent
import com.battlelancer.seriesguide.ui.BaseMessageActivity.ServiceCompletedEvent
import com.battlelancer.seriesguide.util.ImageTools
-import com.battlelancer.seriesguide.util.ImageTools.tmdbOrTvdbStillUrl
import com.battlelancer.seriesguide.util.LanguageTools
import com.battlelancer.seriesguide.util.RatingsTools.initialize
import com.battlelancer.seriesguide.util.RatingsTools.setLink
@@ -75,6 +74,7 @@ import com.battlelancer.seriesguide.util.ViewTools
import com.battlelancer.seriesguide.util.WebTools
import com.battlelancer.seriesguide.util.copyTextToClipboardOnLongClick
import com.battlelancer.seriesguide.util.safeShow
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import com.squareup.picasso.Callback
import com.squareup.picasso.Picasso
import kotlinx.coroutines.Job
@@ -82,6 +82,7 @@ import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import timber.log.Timber
+import java.text.NumberFormat
/**
* Displays general information about a show and, if there is one, the next episode to watch.
@@ -146,11 +147,11 @@ class OverviewFragment() : Fragment(), EpisodeActionsContract {
}
buttonOverviewFavoriteShow.setOnClickListener { onButtonFavoriteClick() }
- containerOverviewEpisodeCard.setOnClickListener { v: View? ->
+ containerOverviewEpisodeCard.setOnClickListener { v: View ->
runIfHasEpisode { episode ->
// display episode details
val intent = EpisodesActivity.intentEpisode(episode.id, requireContext())
- Utils.startActivityWithAnimation(activity, intent, v)
+ requireActivity().startActivityWithAnimation(intent, v)
}
}
@@ -341,10 +342,10 @@ class OverviewFragment() : Fragment(), EpisodeActionsContract {
}
}
- private fun onButtonCommentsClick(v: View?) {
+ private fun onButtonCommentsClick(v: View) {
runIfHasEpisode { episode ->
val i = TraktCommentsActivity.intentEpisode(requireContext(), episode.title, episode.id)
- Utils.startActivityWithAnimation(activity, i, v)
+ requireActivity().startActivityWithAnimation(i, v)
}
}
@@ -470,7 +471,9 @@ class OverviewFragment() : Fragment(), EpisodeActionsContract {
val episodeAbsoluteNumber = episode.absoluteNumber
if (episodeAbsoluteNumber != null
&& episodeAbsoluteNumber > 0 && episodeAbsoluteNumber != number) {
- infoText.append(" (").append(episodeAbsoluteNumber).append(")")
+ infoText.append(" (")
+ .append(NumberFormat.getIntegerInstance().format(episodeAbsoluteNumber.toLong()))
+ .append(")")
}
// release date
@@ -624,7 +627,11 @@ class OverviewFragment() : Fragment(), EpisodeActionsContract {
)
}
- private fun loadEpisodeImage(imageView: ImageView, detailsHiddenView: View, imagePath: String?) {
+ private fun loadEpisodeImage(
+ imageView: ImageView,
+ detailsHiddenView: View,
+ imagePath: String?
+ ) {
if (imagePath.isNullOrEmpty()) {
imageView.setImageDrawable(null)
detailsHiddenView.isGone = true
@@ -643,7 +650,7 @@ class OverviewFragment() : Fragment(), EpisodeActionsContract {
// Try loading image
ImageTools.loadWithPicasso(
requireContext(),
- tmdbOrTvdbStillUrl(imagePath, requireContext(), false)
+ ImageTools.buildEpisodeImageUrl(imagePath, requireContext())
)
.error(R.drawable.ic_photo_gray_24dp)
.into(imageView,
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/SeasonsFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/SeasonsFragment.kt
index 055bf76ea2..c7507e4123 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/SeasonsFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/SeasonsFragment.kt
@@ -13,8 +13,6 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.appcompat.widget.TooltipCompat
-import androidx.core.app.ActivityCompat
-import androidx.core.app.ActivityOptionsCompat
import androidx.core.os.bundleOf
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
@@ -32,6 +30,7 @@ import com.battlelancer.seriesguide.shows.episodes.EpisodesActivity
import com.battlelancer.seriesguide.ui.BaseMessageActivity
import com.battlelancer.seriesguide.ui.dialogs.SingleChoiceDialogFragment
import com.battlelancer.seriesguide.util.ThemeUtils
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
@@ -167,12 +166,7 @@ class SeasonsFragment() : Fragment() {
private val listOnItemClickListener = object : SeasonsAdapter.ItemClickListener {
override fun onItemClick(v: View, seasonRowId: Long) {
val intent = EpisodesActivity.intentSeason(seasonRowId, requireActivity())
- ActivityCompat.startActivity(
- requireActivity(), intent,
- ActivityOptionsCompat
- .makeScaleUpAnimation(v, 0, 0, v.width, v.height)
- .toBundle()
- )
+ requireActivity().startActivityWithAnimation(intent, v)
}
override fun onMoreOptionsClick(v: View, seasonRowId: Long) {
@@ -383,7 +377,7 @@ class SeasonsFragment() : Fragment() {
parentFragmentManager,
R.array.sesorting,
R.array.sesortingData, sortOrder.index,
- SeasonsSettings.KEY_SEASON_SORT_ORDER, R.string.pref_seasonsorting,
+ SeasonsSettings.KEY_SEASON_SORT_ORDER, R.string.sort,
"seasonSortOrderDialog"
)
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/ShowFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/ShowFragment.kt
index b9fa5879c3..30a9433908 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/ShowFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/ShowFragment.kt
@@ -17,6 +17,7 @@ import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.TooltipCompat
import androidx.core.os.bundleOf
+import androidx.core.view.isGone
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
@@ -51,6 +52,7 @@ import com.battlelancer.seriesguide.util.Utils
import com.battlelancer.seriesguide.util.ViewTools
import com.battlelancer.seriesguide.util.copyTextToClipboardOnLongClick
import com.battlelancer.seriesguide.util.safeShow
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import com.google.android.material.button.MaterialButton
import com.google.android.material.snackbar.Snackbar
import com.uwetrottmann.androidutils.AndroidUtils
@@ -111,6 +113,8 @@ class ShowFragment() : Fragment() {
val buttonWebSearch: Button
val buttonComments: Button
val buttonShare: Button
+ val buttonEditNote: Button
+ val textViewNote: TextView
val castLabel: TextView
val castContainer: LinearLayout
val crewLabel: TextView
@@ -147,6 +151,8 @@ class ShowFragment() : Fragment() {
buttonWebSearch = view.findViewById(R.id.buttonShowWebSearch)
buttonComments = view.findViewById(R.id.buttonShowComments)
buttonShare = view.findViewById(R.id.buttonShowShare)
+ buttonEditNote = view.findViewById(R.id.buttonShowNote)
+ textViewNote = view.findViewById(R.id.textViewShowNote)
castLabel = view.findViewById(R.id.labelCast)
castContainer = view.findViewById(R.id.containerCast)
crewLabel = view.findViewById(R.id.labelCrew)
@@ -184,6 +190,11 @@ class ShowFragment() : Fragment() {
}
}
+ // Edit note button
+ binding.buttonEditNote.setOnClickListener {
+ EditNoteDialog(showId).safeShow(parentFragmentManager, "edit-note")
+ }
+
// language button
val buttonLanguage = binding.buttonLanguage
buttonLanguage.setOnClickListener { displayLanguageSettings() }
@@ -382,6 +393,12 @@ class ShowFragment() : Fragment() {
}
}
+ // note
+ binding.textViewNote.apply {
+ text = showForUi.userNote
+ isGone = showForUi.userNote.isEmpty()
+ }
+
// overview
// Source text requires styling, so needs UI context
binding.textViewOverview.text =
@@ -461,7 +478,7 @@ class ShowFragment() : Fragment() {
val showId = showId
if (showId > 0) {
val i = TraktCommentsActivity.intentShow(requireContext(), show.title, showId)
- Utils.startActivityWithAnimation(activity, i, v)
+ requireActivity().startActivityWithAnimation(i, v)
}
}
@@ -485,7 +502,7 @@ class ShowFragment() : Fragment() {
originalSize = true
)
)
- Utils.startActivityWithAnimation(activity, intent, v)
+ requireActivity().startActivityWithAnimation(intent, v)
}
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/ShowViewModel.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/ShowViewModel.kt
index 7f9e4f168d..fbe6fe27cd 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/overview/ShowViewModel.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/overview/ShowViewModel.kt
@@ -38,6 +38,7 @@ class ShowViewModel(application: Application) : AndroidViewModel(application) {
val show: SgShow2,
val releaseTime: String?,
val baseInfo: String,
+ val userNote: String,
val overview: String?,
val languageData: LanguageTools.LanguageData?,
val country: String,
@@ -97,6 +98,7 @@ class ShowViewModel(application: Application) : AndroidViewModel(application) {
show,
timeOrNull,
baseInfo,
+ show.userNoteOrEmpty,
overview,
languageData,
country,
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/search/EpisodeSearchFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/search/EpisodeSearchFragment.kt
index 36f5d43011..f6f53cfb4b 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/search/EpisodeSearchFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/search/EpisodeSearchFragment.kt
@@ -15,7 +15,7 @@ import com.battlelancer.seriesguide.databinding.FragmentSearchBinding
import com.battlelancer.seriesguide.shows.episodes.EpisodesActivity
import com.battlelancer.seriesguide.shows.search.EpisodeSearchFragment.Companion.ARG_SHOW_TITLE
import com.battlelancer.seriesguide.util.TabClickEvent
-import com.battlelancer.seriesguide.util.Utils
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -96,7 +96,7 @@ class EpisodeSearchFragment : BaseSearchFragment() {
private val onItemClickListener = object : EpisodeSearchAdapter.OnItemClickListener {
override fun onItemClick(anchor: View, episodeId: Long) {
EpisodesActivity.intentEpisode(episodeId, requireContext())
- .also { Utils.startActivityWithAnimation(activity, it, anchor) }
+ .also { requireActivity().startActivityWithAnimation(it, anchor) }
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/search/ShowSearchFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/search/ShowSearchFragment.kt
index 39bd6841e1..bc6ba03e1a 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/search/ShowSearchFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/search/ShowSearchFragment.kt
@@ -13,8 +13,6 @@ import android.view.View
import android.view.ViewGroup
import android.widget.GridView
import androidx.appcompat.widget.PopupMenu
-import androidx.core.app.ActivityCompat
-import androidx.core.app.ActivityOptionsCompat
import androidx.core.view.MenuProvider
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
@@ -27,6 +25,7 @@ import com.battlelancer.seriesguide.shows.search.discover.ShowsDiscoverPagingAct
import com.battlelancer.seriesguide.ui.OverviewActivity
import com.battlelancer.seriesguide.ui.widgets.EmptyView
import com.battlelancer.seriesguide.util.TabClickEvent
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@@ -132,13 +131,7 @@ class ShowSearchFragment : BaseSearchFragment() {
override fun onItemClick(anchor: View, showId: Long) {
OverviewActivity.intentShow(requireContext(), showId).let {
- ActivityCompat.startActivity(
- requireActivity(), it,
- ActivityOptionsCompat.makeScaleUpAnimation(
- anchor, 0, 0,
- anchor.width, anchor.height
- ).toBundle()
- )
+ requireActivity().startActivityWithAnimation(it, anchor)
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/search/discover/ShowsDiscoverAdapter.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/search/discover/ShowsDiscoverAdapter.kt
index e30ba89d40..bbc112f8b1 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/search/discover/ShowsDiscoverAdapter.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/search/discover/ShowsDiscoverAdapter.kt
@@ -215,7 +215,7 @@ class ShowsDiscoverAdapter(
}
binding.buttonDiscoverHeader.apply {
setIconResource(R.drawable.ic_filter_white_24dp)
- contentDescription = context.getString(R.string.action_shows_filter)
+ contentDescription = context.getString(R.string.action_filter)
TooltipCompat.setTooltipText(this, contentDescription)
setOnClickListener { view ->
itemClickListener.onHeaderButtonClick(view)
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/search/discover/ShowsDiscoverFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/search/discover/ShowsDiscoverFragment.kt
index d5b04fb11c..bd7827f227 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/search/discover/ShowsDiscoverFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/search/discover/ShowsDiscoverFragment.kt
@@ -28,10 +28,10 @@ import com.battlelancer.seriesguide.traktapi.TraktCredentials
import com.battlelancer.seriesguide.ui.dialogs.L10nDialogFragment
import com.battlelancer.seriesguide.ui.dialogs.LanguagePickerDialogFragment
import com.battlelancer.seriesguide.ui.dialogs.YearPickerDialogFragment
-import com.battlelancer.seriesguide.util.Utils
import com.battlelancer.seriesguide.util.ViewTools
import com.battlelancer.seriesguide.util.findDialog
import com.battlelancer.seriesguide.util.safeShow
+import com.battlelancer.seriesguide.util.startActivityWithAnimation
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.Subscribe
@@ -137,7 +137,7 @@ class ShowsDiscoverFragment : BaseAddShowsFragment() {
}
}
- Utils.startActivityWithAnimation(activity, intent, anchor)
+ requireActivity().startActivityWithAnimation(intent, anchor)
}
override fun onHeaderButtonClick(anchor: View) {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/tools/AddUpdateShowTools.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/tools/AddUpdateShowTools.kt
index 005a38f82a..777af61612 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/tools/AddUpdateShowTools.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/tools/AddUpdateShowTools.kt
@@ -131,6 +131,9 @@ class AddUpdateShowTools @Inject constructor(
if (hexagonShow.customReleaseTimeZone != null) {
show.customReleaseTimeZone = hexagonShow.customReleaseTimeZone
}
+ if (hexagonShow.note != null) {
+ show.userNote = hexagonShow.note
+ }
}
}
@@ -679,6 +682,7 @@ class AddUpdateShowTools @Inject constructor(
it.customReleaseTime = show.customReleaseTime
it.customReleaseDayOffset = show.customReleaseDayOffset
it.customReleaseTimeZone = show.customReleaseTimeZone
+ it.note = show.userNote
it.isRemoved = false
}))
if (!uploadSuccess) return@andThen Err(UpdateResult.ApiErrorStop(HEXAGON))
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/tools/GetShowTools.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/tools/GetShowTools.kt
index f6ae38cccd..4ce0b6d80a 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/tools/GetShowTools.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/tools/GetShowTools.kt
@@ -205,7 +205,9 @@ class GetShowTools @Inject constructor(
posterSmall = poster,
// set desired language, might not be the content language if fallback used above.
language = desiredLanguage,
- lastUpdatedMs = System.currentTimeMillis() // now
+ lastUpdatedMs = System.currentTimeMillis(), // now
+ userNote = null, // If at all, added later via Hexagon or Trakt (VIP only)
+ userNoteTraktId = null // If at all, added later via Trakt
),
seasons = tmdbSeasons
)
diff --git a/app/src/main/java/com/battlelancer/seriesguide/shows/tools/ShowTools2.kt b/app/src/main/java/com/battlelancer/seriesguide/shows/tools/ShowTools2.kt
index 37b2d0f205..2614125f96 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/shows/tools/ShowTools2.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/shows/tools/ShowTools2.kt
@@ -15,7 +15,13 @@ import com.battlelancer.seriesguide.notifications.NotificationService
import com.battlelancer.seriesguide.provider.SeriesGuideContract
import com.battlelancer.seriesguide.provider.SeriesGuideDatabase
import com.battlelancer.seriesguide.provider.SgRoomDatabase
+import com.battlelancer.seriesguide.shows.database.SgShow2
import com.battlelancer.seriesguide.sync.HexagonShowSync
+import com.battlelancer.seriesguide.traktapi.TraktCredentials
+import com.battlelancer.seriesguide.traktapi.TraktTools2
+import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktErrorResponse
+import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktNonNullResponse
+import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktResponse
import com.uwetrottmann.androidutils.AndroidUtils
import com.uwetrottmann.seriesguide.backend.shows.model.SgCloudShow
import dagger.Lazy
@@ -23,6 +29,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.greenrobot.eventbus.EventBus
+import timber.log.Timber
import javax.inject.Inject
import kotlin.collections.set
import com.battlelancer.seriesguide.enums.Result as SgResult
@@ -451,6 +458,131 @@ class ShowTools2 @Inject constructor(
notifyAboutSyncing()
}
+ data class StoreUserNoteResult(val text: String, val traktId: Long?)
+
+ /**
+ * Uploads to Hexagon and Trakt and on success saves to local database,
+ * on failure returns `null`.
+ *
+ * Fails if [noteDraft] exceeds [SgShow2.MAX_USER_NOTE_LENGTH] characters.
+ *
+ * A blank string is treated as an empty string.
+ *
+ * If the final string is empty, an existing note will be deleted at Trakt.
+ *
+ * Returns the stored text (as Trakt may modify it) and optional assigned Trakt ID.
+ */
+ suspend fun storeUserNote(
+ showId: Long,
+ noteDraft: String,
+ noteTraktId: Long?
+ ): StoreUserNoteResult? {
+ val noteText = noteDraft
+ .ifBlank { "" } // Avoid storing useless data, but also Trakt does not allow a blank text
+
+ // Fail if string is too long
+ if (noteText.length > SgShow2.MAX_USER_NOTE_LENGTH) return null
+
+ // Send to Cloud first, Trakt may fail if user is not VIP
+ val isSendToCloudSuccess: Boolean = if (HexagonSettings.isEnabled(context)) {
+ withContext(Dispatchers.Default) {
+ if (isNotConnected(context)) {
+ return@withContext false
+ }
+
+ val showTmdbId = withContext(Dispatchers.IO) {
+ SgRoomDatabase.getInstance(context).sgShow2Helper().getShowTmdbId(showId)
+ }
+ if (showTmdbId == 0) return@withContext false
+
+ val show = SgCloudShow()
+ show.tmdbId = showTmdbId
+ // Must be empty to remove, Cloud ignores null values
+ show.note = noteText
+ return@withContext uploadShowToCloud(show)
+ }
+ } else {
+ true // Not sending to Cloud
+ }
+
+ // If sending to Cloud failed, do not even try Trakt or save to database
+ if (!isSendToCloudSuccess) return null
+
+ var result: StoreUserNoteResult? = StoreUserNoteResult(text = noteText, traktId = null)
+
+ val sendToTrakt = TraktCredentials.get(context).hasCredentials()
+ if (sendToTrakt) {
+ result = withContext(Dispatchers.Default) {
+ if (isNotConnected(context)) {
+ return@withContext null
+ }
+
+ val showTmdbId = withContext(Dispatchers.IO) {
+ SgRoomDatabase.getInstance(context).sgShow2Helper().getShowTmdbId(showId)
+ }
+ if (showTmdbId == 0) return@withContext null
+
+ val trakt = SgApp.getServicesComponent(context).trakt()
+ if (noteText.isEmpty()) {
+ // Delete note
+ if (noteTraktId == null) return@withContext null
+ val response = TraktTools2.deleteNote(trakt, noteTraktId)
+ return@withContext when (response) {
+ is TraktResponse.Success -> {
+ StoreUserNoteResult("", null) // Remove text and Trakt ID
+ }
+
+ is TraktErrorResponse.IsUnauthorized -> {
+ TraktCredentials.get(context).setCredentialsInvalid()
+ null // Abort
+ }
+
+ is TraktErrorResponse.IsNotVip -> {
+ Timber.d("storeUserNote: user is not Trakt VIP, can not delete at Trakt")
+ result // Store as is
+ }
+
+ is TraktErrorResponse.Other -> null // Abort
+ }
+ } else {
+ // Add or update note
+ val response = TraktTools2.saveNoteForShow(trakt.notes(), showTmdbId, noteText)
+ return@withContext when (response) {
+ is TraktNonNullResponse.Success -> {
+ // Store ID and note text from Trakt
+ // (which may shorten or otherwise modify it).
+ val storedText = response.data.notes ?: ""
+ StoreUserNoteResult(storedText, response.data.id)
+ }
+
+ is TraktErrorResponse.IsUnauthorized -> {
+ TraktCredentials.get(context).setCredentialsInvalid()
+ null
+ }
+
+ is TraktErrorResponse.IsNotVip -> {
+ Timber.d("storeUserNote: user is not Trakt VIP, can not upload to Trakt")
+ result // Store as is
+ }
+
+ is TraktErrorResponse.Other -> null // Abort
+ }
+ }
+ }
+ }
+
+ // Do not save to local database if sending to Trakt has failed,
+ // but not if user is just not VIP.
+ if (result == null) return null
+
+ // Save to local database
+ withContext(Dispatchers.IO) {
+ SgRoomDatabase.getInstance(context).sgShow2Helper()
+ .updateUserNote(showId, result.text, result.traktId)
+ }
+ return result
+ }
+
private suspend fun notifyAboutSyncing() {
withContext(Dispatchers.Main) {
// show immediate feedback, also if offline and sync won't go through
@@ -475,6 +607,9 @@ class ShowTools2 @Inject constructor(
return !isConnected
}
+ /**
+ * Calls [HexagonShowSync.upload].
+ */
private suspend fun uploadShowToCloud(show: SgCloudShow): Boolean {
return hexagonShowSync.get().upload(show)
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/streaming/SgWatchProviderHelper.kt b/app/src/main/java/com/battlelancer/seriesguide/streaming/SgWatchProviderHelper.kt
index 4f13d18519..231f458d6c 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/streaming/SgWatchProviderHelper.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/streaming/SgWatchProviderHelper.kt
@@ -55,8 +55,8 @@ interface SgWatchProviderHelper {
@Query("SELECT sg_watch_provider.* FROM sg_watch_provider JOIN sg_watch_provider_show_mappings ON sg_watch_provider.provider_id=sg_watch_provider_show_mappings.provider_id WHERE type=:type GROUP BY _id ORDER BY provider_name COLLATE UNICODE ASC")
fun usedWatchProvidersFlow(type: Int): Flow>
- /* Note: never just get those with filter_local=1 as once a show does not longer use a provider
- it is not longer shown in the filter UI, so it can not be disabled. */
+ /* Note: never just get those with filter_local=1 as once a show does no longer use a provider
+ it is no longer shown in the filter UI, so it can not be disabled. */
@Query("SELECT sg_watch_provider.* FROM sg_watch_provider JOIN sg_watch_provider_show_mappings ON sg_watch_provider.provider_id=sg_watch_provider_show_mappings.provider_id WHERE type=:type AND filter_local=1 GROUP BY _id")
fun filterLocalWatchProviders(type: Int): Flow>
diff --git a/app/src/main/java/com/battlelancer/seriesguide/streaming/WatchProviderFilterDialogFragment.kt b/app/src/main/java/com/battlelancer/seriesguide/streaming/WatchProviderFilterDialogFragment.kt
index 988d0156fa..0299f0b42c 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/streaming/WatchProviderFilterDialogFragment.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/streaming/WatchProviderFilterDialogFragment.kt
@@ -49,12 +49,6 @@ class WatchProviderFilterDialogFragment : AppCompatDialogFragment() {
val binding = DialogWatchProviderFilterBinding.inflate(layoutInflater)
.also { binding = it }
- val titleRes = when (type) {
- Type.SHOWS -> R.string.action_shows_filter
- Type.MOVIES -> R.string.action_movies_filter
- }
- binding.textViewTitle.setText(titleRes)
-
// watch region button
binding.buttonWatchRegion.apply {
text = StreamingSearch.getCurrentRegionOrSelectString(requireContext())
@@ -80,6 +74,7 @@ class WatchProviderFilterDialogFragment : AppCompatDialogFragment() {
}
return MaterialAlertDialogBuilder(requireContext())
+ .setTitle(R.string.action_stream)
.setView(binding.root)
.setPositiveButton(R.string.dismiss, null)
.setNegativeButton(R.string.action_reset) { _, _ ->
diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/HexagonShowSync.kt b/app/src/main/java/com/battlelancer/seriesguide/sync/HexagonShowSync.kt
index fcf97891cd..ef29d26ce9 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/sync/HexagonShowSync.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/sync/HexagonShowSync.kt
@@ -135,7 +135,7 @@ class HexagonShowSync @Inject constructor(
logAndReportHexagon("get shows", e)
return false
}
- if (shows == null || shows.isEmpty()) {
+ if (shows.isNullOrEmpty()) {
// nothing to do here
break
}
@@ -324,6 +324,14 @@ class HexagonShowSync @Inject constructor(
hasUpdates = true
}
}
+ if (show.note != null) {
+ // When merging, only overwrite if note on server is not empty:
+ // this might cause a previously deleted note to get uploaded later, but
+ // that's less bad than deleting a note that only exists on this device.
+ if (!mergeValues || show.note.isNotEmpty()) {
+ update.userNote = show.note
+ }
+ }
// If below properties have changed (new value != old value), should
// trigger a sync for this show.
var scheduleShowUpdate = false
@@ -381,6 +389,8 @@ class HexagonShowSync @Inject constructor(
val tmdbId = localShow.tmdbId ?: continue
val cloudShow = SgCloudShow()
cloudShow.tmdbId = tmdbId
+ // It's fine if some of these are null (and would be ignored by Cloud) as existing shows
+ // on Cloud were downloaded before this.
cloudShow.isFavorite = localShow.favorite
cloudShow.notify = localShow.favorite
cloudShow.isHidden = localShow.hidden
@@ -388,6 +398,7 @@ class HexagonShowSync @Inject constructor(
cloudShow.customReleaseTime = localShow.customReleaseTime
cloudShow.customReleaseDayOffset = localShow.customReleaseDayOffset
cloudShow.customReleaseTimeZone = localShow.customReleaseTimeZone
+ cloudShow.note = localShow.userNote
shows.add(cloudShow)
}
if (shows.size == 0) {
@@ -399,7 +410,7 @@ class HexagonShowSync @Inject constructor(
}
/**
- * Uploads the given list of shows to Hexagon.
+ * Uploads the given list of shows to Hexagon. Returns `true` if upload succeeded.
*/
fun upload(shows: List): Boolean {
if (shows.isEmpty()) {
@@ -430,6 +441,9 @@ class HexagonShowSync @Inject constructor(
return true
}
+ /**
+ * Calls [upload] on the IO dispatcher. Returns `true` if upload succeeded.
+ */
suspend fun upload(show: SgCloudShow): Boolean {
return withContext(Dispatchers.IO) {
upload(listOf(show))
diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/SgSyncAdapter.kt b/app/src/main/java/com/battlelancer/seriesguide/sync/SgSyncAdapter.kt
index c5641008ac..23642947cb 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/sync/SgSyncAdapter.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/sync/SgSyncAdapter.kt
@@ -26,12 +26,13 @@ import com.battlelancer.seriesguide.notifications.NotificationService
import com.battlelancer.seriesguide.provider.SeriesGuideDatabase
import com.battlelancer.seriesguide.settings.UpdateSettings
import com.battlelancer.seriesguide.shows.tools.ShowSync
+import com.battlelancer.seriesguide.shows.tools.ShowTools2
import com.battlelancer.seriesguide.sync.SyncOptions.SyncType
+import com.battlelancer.seriesguide.traktapi.SgTrakt
import com.battlelancer.seriesguide.traktapi.TraktCredentials
import com.battlelancer.seriesguide.util.TaskManager
import com.uwetrottmann.androidutils.AndroidUtils
import com.uwetrottmann.tmdb2.services.ConfigurationService
-import com.uwetrottmann.trakt5.services.Sync
import dagger.Lazy
import timber.log.Timber
import javax.inject.Inject
@@ -52,7 +53,10 @@ class SgSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, tru
lateinit var hexagonTools: Lazy
@Inject
- lateinit var traktSync: Lazy
+ lateinit var trakt: Lazy
+
+ @Inject
+ lateinit var showTools2: Lazy
@Inject
lateinit var movieTools: Lazy
@@ -189,9 +193,12 @@ class SgSyncAdapter(context: Context) : AbstractThreadedSyncAdapter(context, tru
// sync with trakt (only ratings if hexagon is enabled)
if (TraktCredentials.get(context).hasCredentials()) {
val resultTraktSync = TraktSync(
- context, movieTools.get(),
- traktSync.get(), progress
- ).sync(currentTime, isHexagonEnabled)
+ context,
+ showTools2.get(),
+ movieTools.get(),
+ trakt.get(),
+ progress
+ ).sync(isHexagonEnabled)
// don't overwrite failure
if (resultCode == UpdateResult.SUCCESS) {
resultCode = resultTraktSync
diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/SyncProgress.java b/app/src/main/java/com/battlelancer/seriesguide/sync/SyncProgress.java
index a87654f99d..bd24a4433d 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/sync/SyncProgress.java
+++ b/app/src/main/java/com/battlelancer/seriesguide/sync/SyncProgress.java
@@ -23,6 +23,7 @@ public enum Step {
TRAKT(R.string.trakt, 0),
TRAKT_EPISODES(R.string.trakt, R.string.episodes),
TRAKT_RATINGS(R.string.trakt, R.string.ratings),
+ TRAKT_NOTES(R.string.trakt, R.string.title_notes),
TRAKT_MOVIES(R.string.trakt, R.string.movies);
public final int serviceRes;
diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/TraktEpisodeSync.kt b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktEpisodeSync.kt
index ed30ea5309..6c3a4fc3dc 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/sync/TraktEpisodeSync.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktEpisodeSync.kt
@@ -1,5 +1,5 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2017-2024 Uwe Trottmann
package com.battlelancer.seriesguide.sync
@@ -49,7 +49,7 @@ class TraktEpisodeSync(
showRowId: Long,
flag: Flag
): Boolean {
- if (tmdbIdsToTraktShow == null || tmdbIdsToTraktShow.isEmpty()) {
+ if (tmdbIdsToTraktShow.isNullOrEmpty()) {
return true // no watched/collected shows on Trakt, done.
}
val traktShow = tmdbIdsToTraktShow[showTmdbId]
@@ -219,7 +219,9 @@ class TraktEpisodeSync(
if (isInitialSync) {
// upload all watched/collected episodes of the show
// do in between processing to stretch uploads over longer time periods
- uploadShow(traktSync!!, showId, showTraktId, flag)
+ if (!uploadShow(traktSync!!, showId, showTraktId, flag)) {
+ return false // uploading failed, stop and try again later
+ }
uploadedShowsCount++
} else {
// Set all watched/collected episodes of show not watched/collected,
@@ -470,6 +472,8 @@ class TraktEpisodeSync(
/**
* Uploads all watched/collected episodes for the given show to Trakt.
+ *
+ * Returns true if the upload was successful or there was nothing to upload.
*/
private fun uploadShow(
traktSync: TraktSync,
@@ -492,6 +496,8 @@ class TraktEpisodeSync(
/**
* Uploads all the given watched/collected episodes of the given show to Trakt.
+ *
+ * Returns true if the upload was successful.
*/
private fun upload(
traktSync: TraktSync,
@@ -538,6 +544,7 @@ class TraktEpisodeSync(
Flag.WATCHED -> {
helper.getWatchedEpisodesForTraktSync(seasonId)
}
+
Flag.COLLECTED -> {
helper.getCollectedEpisodesForTraktSync(seasonId)
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/TraktMovieSync.kt b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktMovieSync.kt
index 1da83f1d36..d1032c499a 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/sync/TraktMovieSync.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktMovieSync.kt
@@ -1,20 +1,17 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2017-2024 Uwe Trottmann
package com.battlelancer.seriesguide.sync
import android.content.ContentProviderOperation
import android.content.OperationApplicationException
-import androidx.preference.PreferenceManager
import com.battlelancer.seriesguide.movies.database.SgMovieFlags
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Movies
import com.battlelancer.seriesguide.provider.SgRoomDatabase
import com.battlelancer.seriesguide.traktapi.SgTrakt
-import com.battlelancer.seriesguide.traktapi.TraktCredentials
import com.battlelancer.seriesguide.traktapi.TraktSettings
import com.battlelancer.seriesguide.util.DBUtils
import com.battlelancer.seriesguide.util.Errors
-import com.uwetrottmann.androidutils.AndroidUtils
import com.uwetrottmann.trakt5.entities.BaseMovie
import com.uwetrottmann.trakt5.entities.LastActivityMore
import com.uwetrottmann.trakt5.entities.MovieIds
@@ -47,31 +44,29 @@ class TraktMovieSync(
* thread.
*/
fun syncLists(activity: LastActivityMore): Boolean {
- if (activity.collected_at == null) {
+ val collectedAt = activity.collected_at
+ if (collectedAt == null) {
Timber.e("syncLists: null collected_at")
return false
}
- if (activity.watchlisted_at == null) {
+ val watchlistedAt = activity.watchlisted_at
+ if (watchlistedAt == null) {
Timber.e("syncLists: null watchlisted_at")
return false
}
- if (activity.watched_at == null) {
+ val watchedAt = activity.watched_at
+ if (watchedAt == null) {
Timber.e("syncLists: null watched_at")
return false
}
- val merging = !TraktSettings.hasMergedMovies(context)
- if (!merging && !TraktSettings.isMovieListsChanged(
- context, activity.collected_at, activity.watchlisted_at, activity.watched_at
- )) {
+ val isInitialSync = TraktSettings.isInitialSyncMovies(context)
+ if (!isInitialSync
+ && !TraktSettings.isMovieListsChanged(context, collectedAt, watchlistedAt, watchedAt)) {
Timber.d("syncLists: no changes")
return true
}
- if (!TraktCredentials.get(context).hasCredentials()) {
- return false
- }
-
// Download Trakt state.
val collection = downloadCollection() ?: return false
val watchlist = downloadWatchlist() ?: return false
@@ -97,7 +92,7 @@ class TraktMovieSync(
val playsOnTrakt = watchedWithPlays.remove(tmdbId)
val isWatchedOnTrakt = playsOnTrakt != null
- if (merging) {
+ if (isInitialSync) {
// Mark movie for upload if missing from Trakt collection or watchlist
// or if not watched on Trakt.
// Note: If watches were removed on Trakt in the meanwhile, this would re-add them.
@@ -177,7 +172,7 @@ class TraktMovieSync(
batch.clear() // release for gc
// merge on first run
- if (merging) {
+ if (isInitialSync) {
// Upload movies not in Trakt collection, watchlist or watched history.
if (uploadFlagsNotOnTrakt(
toCollectOnTrakt,
@@ -185,10 +180,7 @@ class TraktMovieSync(
toSetWatchedOnTrakt
)) {
// set merge successful
- PreferenceManager.getDefaultSharedPreferences(context)
- .edit()
- .putBoolean(TraktSettings.KEY_HAS_MERGED_MOVIES, true)
- .apply()
+ TraktSettings.setInitialSyncMoviesCompleted(context)
} else {
return false
}
@@ -202,9 +194,9 @@ class TraktMovieSync(
// store last activity timestamps
TraktSettings.storeLastMoviesChangedAt(
context,
- activity.collected_at,
- activity.watchlisted_at,
- activity.watched_at
+ collectedAt,
+ watchlistedAt,
+ watchedAt
)
// if movies were added, ensure ratings for them are downloaded next
if (collection.isNotEmpty() || watchlist.isNotEmpty() || watchedWithPlays.isNotEmpty()) {
@@ -321,10 +313,6 @@ class TraktMovieSync(
return true
}
- if (!AndroidUtils.isNetworkConnected(context)) {
- return false // Fail, no connection is available.
- }
-
// Upload.
var action = ""
val items = SyncItems()
diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/TraktNotesSync.kt b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktNotesSync.kt
new file mode 100644
index 0000000000..b2c1e0b027
--- /dev/null
+++ b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktNotesSync.kt
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2024 Uwe Trottmann
+
+package com.battlelancer.seriesguide.sync
+
+import com.battlelancer.seriesguide.provider.SgRoomDatabase
+import com.battlelancer.seriesguide.shows.database.SgShow2Helper
+import com.battlelancer.seriesguide.traktapi.SgTrakt
+import com.battlelancer.seriesguide.traktapi.TraktSettings
+import com.battlelancer.seriesguide.traktapi.TraktTools2
+import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktErrorResponse
+import com.battlelancer.seriesguide.traktapi.TraktTools2.TraktNonNullResponse
+import com.battlelancer.seriesguide.util.Errors
+import com.battlelancer.seriesguide.util.TimeTools
+import com.uwetrottmann.trakt5.TraktV2
+import com.uwetrottmann.trakt5.entities.NoteResponse
+import com.uwetrottmann.trakt5.entities.UserSlug
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.threeten.bp.OffsetDateTime
+import timber.log.Timber
+
+/**
+ * Syncs notes, currently only for shows, with Trakt.
+ *
+ * This will do nothing if the user is not a Trakt VIP.
+ */
+class TraktNotesSync(
+ private val traktSync: TraktSync
+) {
+ private val context = traktSync.context
+ private val showHelper = SgRoomDatabase.getInstance(context).sgShow2Helper()
+
+ /**
+ * When a note is new or different at Trakt, updates the local show (if it is added).
+ * So if the local show has a note, it is overwritten (server is source of truth).
+ * When a note is removed (or rather does not exist) at Trakt, will either on the initial sync
+ * upload the note or for consecutive syncs remove the note on the local show.
+ */
+ fun syncForShows(updatedAt: OffsetDateTime?): Boolean {
+ if (updatedAt == null) {
+ Timber.e("syncForShows: null updatedAt")
+ return false
+ }
+
+ val isInitialSync = TraktSettings.isInitialSyncShowNotes(context)
+
+ // Do not sync if notes have not changed, or is not initial sync
+ val lastUpdatedAt = TraktSettings.getLastNotesUpdatedAt(context)
+ if (!isInitialSync && !TimeTools.isAfterMillis(updatedAt, lastUpdatedAt)) {
+ Timber.d("syncForShows: no changes since %tF %tT", lastUpdatedAt, lastUpdatedAt)
+ return true
+ }
+
+ // As this runs only if a note changes, optimize for memory, not for CPU
+ val tmdbIdsToLocalShowIds = traktSync.showTools2.getTmdbIdsToShowIds()
+ val showIdsWithNotesToUploadOrRemove = showHelper.getShowIdsWithNotes()
+
+ val action = "get notes"
+ var page = 1
+ var pageCount: Int
+ do {
+ try {
+ val response = traktSync.users
+ .notes(UserSlug.ME, "shows", page, null, null)
+ .execute()
+
+ if (!response.isSuccessful) {
+ if (TraktV2.isNotVip(response)) {
+ // Do not error; also do not set initial sync complete or last updated in
+ // case the user gets VIP later, or loses VIP and gets it again.
+ Timber.d("syncForShows: user is not VIP, giving up")
+ return true
+ }
+ if (SgTrakt.isUnauthorized(context, response)) {
+ return false
+ }
+ Errors.logAndReport(action, response)
+ return false
+ }
+
+ val noteResponses = response.body()
+ if (noteResponses == null) {
+ Errors.logAndReport(action, response, "body is null")
+ return false
+ }
+
+ processShowNotes(
+ noteResponses,
+ tmdbIdsToLocalShowIds,
+ showIdsWithNotesToUploadOrRemove
+ )
+
+ pageCount = TraktV2.getPageCount(response) ?: 1
+ page++
+ } catch (e: Exception) {
+ Errors.logAndReport(action, e)
+ return false
+ }
+ } while (page <= pageCount)
+
+ if (isInitialSync) {
+ if (!uploadNotesForShows(showIdsWithNotesToUploadOrRemove)) return false
+ } else {
+ // Remove notes from shows that are not on Trakt, meaning their note got removed
+ Timber.d(
+ "syncForShows: remove notes no longer on Trakt for %s shows",
+ showIdsWithNotesToUploadOrRemove.size
+ )
+ showHelper.updateUserNotes(showIdsWithNotesToUploadOrRemove
+ .associateWith { SgShow2Helper.NoteUpdate("", null) })
+ }
+
+ if (isInitialSync) {
+ TraktSettings.setInitialSyncShowNotesCompleted(context)
+ }
+ TraktSettings.storeLastNotesUpdatedAt(context, updatedAt)
+
+ return true
+ }
+
+ private fun processShowNotes(
+ response: List,
+ tmdbIdsToLocalShowIds: Map,
+ showIdsWithNotesToUploadOrRemove: MutableList
+ ) {
+ if (response.isEmpty()) {
+ Timber.d("processShowNotes: nothing to process")
+ return
+ }
+
+ val noteUpdates = mutableMapOf()
+
+ for (note in response) {
+ val showTmdbId = note.show?.ids?.tmdb
+ ?: continue // Need a TMDB ID
+ val noteText = note.note?.notes
+ ?: continue // Need a note
+ val noteTraktId = note.note?.id
+ ?: continue // Need its Trakt ID
+
+ val localShowId = tmdbIdsToLocalShowIds[showTmdbId]
+ ?: continue // Show not in database
+ showIdsWithNotesToUploadOrRemove.remove(localShowId)
+ val localShow = showHelper.getShowWithNote(localShowId)
+ ?: continue // Show was removed in the meantime
+
+ // Not sure how it could happen, but also update when the Trakt ID has changed to
+ // always have the correct one.
+ if (localShow.userNote != noteText || localShow.userNoteTraktId != noteTraktId) {
+ noteUpdates[localShowId] = SgShow2Helper.NoteUpdate(noteText, noteTraktId)
+ }
+ }
+
+ Timber.d("processShowNotes: updating note text or ID for %s shows", noteUpdates.size)
+ showHelper.updateUserNotes(noteUpdates)
+ }
+
+ /**
+ * Adds the notes at Trakt and updates the local show with the saved text (Trakt may modify it)
+ * and note ID.
+ *
+ * Returns whether all notes were successfully uploaded.
+ */
+ private fun uploadNotesForShows(showIdsWithNotesToUpload: MutableList): Boolean {
+ Timber.d("uploadNotesForShows: uploading for %s shows", showIdsWithNotesToUpload.size)
+
+ // Cache service
+ val traktNotes = traktSync.trakt.notes()
+
+ val noteUpdates = mutableMapOf()
+ try {
+ for (showId in showIdsWithNotesToUpload) {
+ val show = showHelper.getShowWithNote(showId)
+ ?: continue // Show was removed in the meantime
+ val showTmdbId = show.tmdbId
+ ?: continue // Need a TMDB ID to upload
+ val noteText = show.userNote
+ ?.ifBlank { null } // Trakt does not allow blank text
+ ?: continue // Note got removed in the meantime
+
+ // If this thread is interrupted throws InterruptedException
+ val storedNote = runBlocking(Dispatchers.Default) {
+ val response = TraktTools2
+ .saveNoteForShow(traktNotes, showTmdbId, noteText)
+ when (response) {
+ is TraktNonNullResponse.Success -> response.data
+ is TraktErrorResponse.IsNotVip -> {
+ // Note: if failing due to not VIP, downloaded notes before, which would
+ // have required VIP; so assume it expired when getting until this point.
+ Timber.e("uploadNotesForShows: user is no longer VIP")
+ null
+ }
+
+ else -> null
+ }
+ }
+
+ if (storedNote == null) {
+ return false // Stop uploading
+ }
+
+ val storedText = storedNote.notes ?: ""
+ val noteTraktId = storedNote.id
+ noteUpdates[showId] = SgShow2Helper.NoteUpdate(storedText, noteTraktId)
+ }
+ } finally {
+ Timber.d("uploadNotesForShows: uploaded for %s shows", noteUpdates.size)
+ // In any case, save updates to any already sent notes
+ showHelper.updateUserNotes(noteUpdates)
+ }
+
+ return true
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/TraktRatingsSync.kt b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktRatingsSync.kt
index 5b56519383..12ddbf7d07 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/sync/TraktRatingsSync.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktRatingsSync.kt
@@ -10,7 +10,6 @@ import androidx.preference.PreferenceManager
import com.battlelancer.seriesguide.provider.SeriesGuideContract.Movies
import com.battlelancer.seriesguide.provider.SgRoomDatabase
import com.battlelancer.seriesguide.traktapi.SgTrakt
-import com.battlelancer.seriesguide.traktapi.TraktCredentials
import com.battlelancer.seriesguide.traktapi.TraktSettings
import com.battlelancer.seriesguide.util.DBUtils
import com.battlelancer.seriesguide.util.Errors
@@ -48,10 +47,6 @@ class TraktRatingsSync(
return true
}
- if (!TraktCredentials.get(context).hasCredentials()) {
- return false
- }
-
// download rated shows
val ratedShows: List?
try {
@@ -133,10 +128,6 @@ class TraktRatingsSync(
return true
}
- if (!TraktCredentials.get(context).hasCredentials()) {
- return false
- }
-
// download rated episodes
val ratedEpisodes: List?
try {
@@ -217,10 +208,6 @@ class TraktRatingsSync(
return true
}
- if (!TraktCredentials.get(context).hasCredentials()) {
- return false
- }
-
// download rated shows
val ratedMovies: List?
try {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/sync/TraktSync.kt b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktSync.kt
index 099c5fc91c..45071926ba 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/sync/TraktSync.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/sync/TraktSync.kt
@@ -1,22 +1,20 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2017-2024 Uwe Trottmann
package com.battlelancer.seriesguide.sync
import android.content.Context
-import androidx.preference.PreferenceManager
import com.battlelancer.seriesguide.R
import com.battlelancer.seriesguide.SgApp
import com.battlelancer.seriesguide.movies.tools.MovieTools
+import com.battlelancer.seriesguide.shows.tools.ShowTools2
import com.battlelancer.seriesguide.traktapi.SgTrakt
-import com.battlelancer.seriesguide.traktapi.TraktCredentials
import com.battlelancer.seriesguide.traktapi.TraktSettings
import com.battlelancer.seriesguide.traktapi.TraktTools2
import com.battlelancer.seriesguide.util.Errors
import com.github.michaelbull.result.getOrElse
import com.uwetrottmann.androidutils.AndroidUtils
import com.uwetrottmann.trakt5.entities.LastActivityMore
-import com.uwetrottmann.trakt5.services.Sync
import retrofit2.Response
import timber.log.Timber
@@ -27,20 +25,36 @@ import timber.log.Timber
*/
class TraktSync(
val context: Context,
+ val showTools2: ShowTools2,
val movieTools: MovieTools,
- val sync: Sync,
+ val trakt: SgTrakt,
val progress: SyncProgress
) {
+
+ // Cache services
+ val sync = trakt.sync()
+ val users = trakt.users()
+
+ private fun noConnection(): Boolean {
+ return if (AndroidUtils.isNetworkConnected(context)) {
+ false
+ } else {
+ progress.recordError()
+ true
+ }
+ }
+
/**
* To not conflict with Hexagon sync, can turn on [onlyRatings] so only
* ratings are synced.
*/
- fun sync(currentTime: Long, onlyRatings: Boolean): SgSyncAdapter.UpdateResult {
+ fun sync(onlyRatings: Boolean): SgSyncAdapter.UpdateResult {
progress.publish(SyncProgress.Step.TRAKT)
- if (!AndroidUtils.isNetworkConnected(context)) {
- progress.recordError()
- return SgSyncAdapter.UpdateResult.INCOMPLETE
- }
+ // While responses might get returned from the disk cache,
+ // this is not desirable when syncing, so frequently check for a network connection.
+ // Note: looked into creating a separate HTTP client without cache, but it makes sense
+ // to keep one as some of the responses are re-used in other parts of the app.
+ if (noConnection()) return SgSyncAdapter.UpdateResult.INCOMPLETE
// Get last activity timestamps.
val lastActivity = TraktTools2.getLastActivity(context)
@@ -60,21 +74,15 @@ class TraktSync(
if (!onlyRatings) {
// Download and upload episode watched and collected flags.
progress.publish(SyncProgress.Step.TRAKT_EPISODES)
- if (!AndroidUtils.isNetworkConnected(context)) {
- progress.recordError()
- return SgSyncAdapter.UpdateResult.INCOMPLETE
- }
- if (!syncEpisodes(tmdbIdsToShowIds, lastActivity.episodes, currentTime)) {
+ if (noConnection()) return SgSyncAdapter.UpdateResult.INCOMPLETE
+ if (!syncEpisodes(tmdbIdsToShowIds, lastActivity.episodes)) {
progress.recordError()
return SgSyncAdapter.UpdateResult.INCOMPLETE
}
}
// Download episode ratings.
progress.publish(SyncProgress.Step.TRAKT_RATINGS)
- if (!AndroidUtils.isNetworkConnected(context)) {
- progress.recordError()
- return SgSyncAdapter.UpdateResult.INCOMPLETE
- }
+ if (noConnection()) return SgSyncAdapter.UpdateResult.INCOMPLETE
if (!ratingsSync.downloadForEpisodes(lastActivity.episodes.rated_at)) {
progress.recordError()
return SgSyncAdapter.UpdateResult.INCOMPLETE
@@ -82,24 +90,27 @@ class TraktSync(
// SHOWS
// Download show ratings.
- if (!AndroidUtils.isNetworkConnected(context)) {
- progress.recordError()
- return SgSyncAdapter.UpdateResult.INCOMPLETE
- }
+ if (noConnection()) return SgSyncAdapter.UpdateResult.INCOMPLETE
if (!ratingsSync.downloadForShows(lastActivity.shows.rated_at)) {
progress.recordError()
return SgSyncAdapter.UpdateResult.INCOMPLETE
}
+ // Download notes
+ if (!onlyRatings) {
+ progress.publish(SyncProgress.Step.TRAKT_NOTES)
+ if (noConnection()) return SgSyncAdapter.UpdateResult.INCOMPLETE
+ if (!TraktNotesSync(this).syncForShows(lastActivity.notes.updated_at)) {
+ progress.recordError()
+ return SgSyncAdapter.UpdateResult.INCOMPLETE
+ }
+ }
}
// MOVIES
progress.publish(SyncProgress.Step.TRAKT_MOVIES)
// Sync watchlist, collection and watched movies.
if (!onlyRatings) {
- if (!AndroidUtils.isNetworkConnected(context)) {
- progress.recordError()
- return SgSyncAdapter.UpdateResult.INCOMPLETE
- }
+ if (noConnection()) return SgSyncAdapter.UpdateResult.INCOMPLETE
if (!TraktMovieSync(this).syncLists(lastActivity.movies)) {
progress.recordError()
return SgSyncAdapter.UpdateResult.INCOMPLETE
@@ -109,10 +120,7 @@ class TraktSync(
}
// Download movie ratings.
progress.publish(SyncProgress.Step.TRAKT_RATINGS)
- if (!AndroidUtils.isNetworkConnected(context)) {
- progress.recordError()
- return SgSyncAdapter.UpdateResult.INCOMPLETE
- }
+ if (noConnection()) return SgSyncAdapter.UpdateResult.INCOMPLETE
if (!ratingsSync.downloadForMovies(lastActivity.movies.rated_at)) {
progress.recordError()
return SgSyncAdapter.UpdateResult.INCOMPLETE
@@ -128,17 +136,12 @@ class TraktSync(
*/
private fun syncEpisodes(
tmdbIdsToShowIds: Map,
- lastActivity: LastActivityMore,
- currentTime: Long
+ lastActivity: LastActivityMore
): Boolean {
- if (!TraktCredentials.get(context).hasCredentials()) {
- return false // Auth was removed.
- }
-
// Download flags.
// If initial sync, upload any flags missing on Trakt
// otherwise clear all local flags not on Trakt.
- val isInitialSync = !TraktSettings.hasMergedEpisodes(context)
+ val isInitialSync = TraktSettings.isInitialSyncEpisodes(context)
// Watched episodes.
val episodeSync = TraktEpisodeSync(this)
@@ -153,14 +156,10 @@ class TraktSync(
return false
}
- val editor = PreferenceManager.getDefaultSharedPreferences(context).edit()
if (isInitialSync) {
// Success, set initial sync as complete.
- editor.putBoolean(TraktSettings.KEY_HAS_MERGED_EPISODES, true)
+ TraktSettings.setInitialSyncEpisodesCompleted(context)
}
- // Success, set last sync time to now.
- editor.putLong(TraktSettings.KEY_LAST_FULL_EPISODE_SYNC, currentTime)
- editor.apply()
return true
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/tmdbapi/TmdbTools.kt b/app/src/main/java/com/battlelancer/seriesguide/tmdbapi/TmdbTools.kt
index cb11e0ddf9..0b910ff403 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/tmdbapi/TmdbTools.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/tmdbapi/TmdbTools.kt
@@ -22,28 +22,32 @@ object TmdbTools {
}
}
- private const val BASE_URL = "https://www.themoviedb.org/"
- private const val PATH_TV = "tv/"
- private const val PATH_MOVIES = "movie/"
- private const val PATH_PERSON = "person/"
+ private const val BASE_URL = "https://www.themoviedb.org"
+ private const val PATH_TV = "tv"
+ private const val PATH_MOVIES = "movie"
+ private const val PATH_PERSON = "person"
@JvmStatic
fun buildEpisodeUrl(showTmdbId: Int, season: Int, episode: Int): String {
- return "$BASE_URL$PATH_TV$showTmdbId/season/$season/episode/$episode"
+ return "$BASE_URL/$PATH_TV/$showTmdbId/season/$season/episode/$episode"
}
@JvmStatic
fun buildShowUrl(showTmdbId: Int): String {
- return BASE_URL + PATH_TV + showTmdbId
+ return "$BASE_URL/$PATH_TV/$showTmdbId"
}
@JvmStatic
fun buildMovieUrl(movieTmdbId: Int): String {
- return BASE_URL + PATH_MOVIES + movieTmdbId
+ return "$BASE_URL/$PATH_MOVIES/$movieTmdbId"
}
fun buildPersonUrl(personTmdbId: Int): String {
- return BASE_URL + PATH_PERSON + personTmdbId
+ return "$BASE_URL/$PATH_PERSON/$personTmdbId"
+ }
+
+ fun buildMovieReleaseDatesUrl(movieTmdbId: Int): String {
+ return "${buildMovieUrl(movieTmdbId)}/releases"
}
/**
diff --git a/app/src/main/java/com/battlelancer/seriesguide/traktapi/SgTrakt.kt b/app/src/main/java/com/battlelancer/seriesguide/traktapi/SgTrakt.kt
index 54d5d245a9..f46a4030fd 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/traktapi/SgTrakt.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/traktapi/SgTrakt.kt
@@ -41,15 +41,6 @@ class SgTrakt(
}
companion object {
- /**
- * Check if the request was unauthorized.
- *
- * @see isUnauthorized accepting context
- */
- @JvmStatic
- fun isUnauthorized(response: Response<*>): Boolean {
- return response.code() == 401
- }
/**
* Returns if the request was not authorized. If it was, also calls
@@ -57,7 +48,7 @@ class SgTrakt(
*/
@JvmStatic
fun isUnauthorized(context: Context, response: Response<*>): Boolean {
- if (response.code() == 401) {
+ if (isUnauthorized(response)) {
// current access token is invalid, remove it and notify user to re-connect
TraktCredentials.get(context).setCredentialsInvalid()
return true
@@ -66,13 +57,6 @@ class SgTrakt(
}
}
- /**
- * Check if the associated [Trakt account is locked](https://trakt.docs.apiary.io/#introduction/locked-user-account).
- */
- fun isAccountLocked(response: Response<*>): Boolean {
- return response.code() == 423
- }
-
fun checkForTraktError(trakt: TraktV2, response: Response<*>): String? {
val error = trakt.checkForTraktError(response)
return if (error?.message != null) {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktAuthActivityModel.kt b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktAuthActivityModel.kt
index 84f0d1455b..9f786f4dac 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktAuthActivityModel.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktAuthActivityModel.kt
@@ -1,18 +1,17 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2021-2024 Uwe Trottmann
package com.battlelancer.seriesguide.traktapi
import android.app.Application
-import androidx.core.content.edit
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
-import androidx.preference.PreferenceManager
import com.battlelancer.seriesguide.SgApp
import com.battlelancer.seriesguide.jobs.NetworkJobProcessor
import com.battlelancer.seriesguide.util.Errors
import com.uwetrottmann.androidutils.AndroidUtils
+import com.uwetrottmann.trakt5.TraktV2
import com.uwetrottmann.trakt5.entities.AccessToken
import com.uwetrottmann.trakt5.entities.Settings
import kotlinx.coroutines.Dispatchers
@@ -97,23 +96,8 @@ class TraktAuthActivityModel(application: Application) : AndroidViewModel(applic
}
// reset sync state before hasCredentials may return true
- NetworkJobProcessor(getApplication())
- .removeObsoleteJobs(false)
- PreferenceManager.getDefaultSharedPreferences(getApplication()).edit {
- // make next sync merge local watched and collected episodes with those on trakt
- putBoolean(TraktSettings.KEY_HAS_MERGED_EPISODES, false)
- // make next sync merge local movies with those on trakt
- putBoolean(TraktSettings.KEY_HAS_MERGED_MOVIES, false)
-
- // make sure the next sync will run a full episode sync
- putLong(TraktSettings.KEY_LAST_FULL_EPISODE_SYNC, 0)
- // make sure the next sync will download all watched movies
- putLong(TraktSettings.KEY_LAST_MOVIES_WATCHED_AT, 0)
- // make sure the next sync will download all ratings
- putLong(TraktSettings.KEY_LAST_SHOWS_RATED_AT, 0)
- putLong(TraktSettings.KEY_LAST_EPISODES_RATED_AT, 0)
- putLong(TraktSettings.KEY_LAST_MOVIES_RATED_AT, 0)
- }
+ NetworkJobProcessor(getApplication()).removeObsoleteJobs(false)
+ TraktSettings.resetToInitialSync(getApplication())
// store the access token, refresh token and expiry time
TraktCredentials.get(getApplication()).storeAccessToken(accessToken)
@@ -147,12 +131,12 @@ class TraktAuthActivityModel(application: Application) : AndroidViewModel(applic
SgTrakt.checkForTraktError(trakt, response)
)
return@withContext when {
- SgTrakt.isUnauthorized(response) -> {
+ TraktV2.isUnauthorized(response) -> {
// access token already is invalid, remove it :(
TraktCredentials.get(getApplication()).removeCredentials()
ConnectResult(TraktResult.AUTH_ERROR)
}
- SgTrakt.isAccountLocked(response) -> {
+ TraktV2.isAccountLocked(response) -> {
ConnectResult(TraktResult.ACCOUNT_LOCKED)
}
else -> {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktComments.kt b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktComments.kt
index 106cbf046c..b33bd678ab 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktComments.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktComments.kt
@@ -109,7 +109,7 @@ class TraktComments(
errorMessageOrNull = context.getString(R.string.error_delete_comment)
} else if (response.code() == 404) {
errorMessageOrNull = context.getString(R.string.trakt_error_not_exists)
- } else if (SgTrakt.isUnauthorized(response)) {
+ } else if (TraktV2.isUnauthorized(response)) {
// for users banned from posting comments requests also return 401
// so do not sign out if an error header does not indicate the token is invalid
val authHeader = response.headers()["WWW-Authenticate"]
diff --git a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktCredentials.kt b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktCredentials.kt
index 01b11acd0a..68c404e7c6 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktCredentials.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktCredentials.kt
@@ -201,7 +201,7 @@ class TraktCredentials private constructor(context: Context) {
refreshToken = token.refresh_token
expiresIn = token.expires_in?.toLong() ?: 0
} else {
- if (!SgTrakt.isUnauthorized(response)) {
+ if (!TraktV2.isUnauthorized(response)) {
Errors.logAndReport("refresh access token", response)
}
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktSettings.java b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktSettings.java
deleted file mode 100644
index ef56a48e5b..0000000000
--- a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktSettings.java
+++ /dev/null
@@ -1,229 +0,0 @@
-// Copyright 2023 Uwe Trottmann
-// SPDX-License-Identifier: Apache-2.0
-
-package com.battlelancer.seriesguide.traktapi;
-
-import android.content.Context;
-import android.text.format.DateUtils;
-import androidx.preference.PreferenceManager;
-import com.battlelancer.seriesguide.util.TimeTools;
-import org.threeten.bp.OffsetDateTime;
-
-/**
- * Settings related to trakt.tv integration.
- */
-public class TraktSettings {
-
- public static final String KEY_LAST_ACTIVITY_DOWNLOAD
- = "com.battlelancer.seriesguide.lasttraktupdate";
-
- public static final String KEY_LAST_SHOWS_RATED_AT
- = "trakt.last_activity.shows.rated";
-
- public static final String KEY_LAST_EPISODES_WATCHED_AT
- = "trakt.last_activity.episodes.watched";
-
- public static final String KEY_LAST_EPISODES_COLLECTED_AT
- = "trakt.last_activity.episodes.collected";
-
- public static final String KEY_LAST_EPISODES_RATED_AT
- = "trakt.last_activity.episodes.rated";
-
- public static final String KEY_LAST_MOVIES_WATCHLISTED_AT
- = "trakt.last_activity.movies.watchlisted";
-
- public static final String KEY_LAST_MOVIES_COLLECTED_AT
- = "trakt.last_activity.movies.collected";
-
- public static final String KEY_LAST_MOVIES_RATED_AT
- = "trakt.last_activity.movies.rated";
-
- public static final String KEY_LAST_MOVIES_WATCHED_AT
- = "trakt.last_activity.movies.watched";
-
- public static final String KEY_LAST_FULL_EPISODE_SYNC
- = "com.battlelancer.seriesguide.trakt.lastfullsync";
-
- public static final String KEY_AUTO_ADD_TRAKT_SHOWS
- = "com.battlelancer.seriesguide.autoaddtraktshows";
-
- public static final String KEY_HAS_MERGED_EPISODES =
- "com.battlelancer.seriesguide.trakt.mergedepisodes";
-
- public static final String KEY_HAS_MERGED_MOVIES
- = "com.battlelancer.seriesguide.trakt.mergedmovies";
-
- public static final String KEY_QUICK_CHECKIN
- = "com.battlelancer.seriesguide.trakt.quickcheckin";
-
- private static final long FULL_SYNC_INTERVAL_MILLIS = 24 * DateUtils.HOUR_IN_MILLIS;
-
- /**
- * The last time trakt episode activity was successfully downloaded.
- */
- public static long getLastActivityDownloadTime(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_ACTIVITY_DOWNLOAD, System.currentTimeMillis());
- }
-
- /**
- * The last time show ratings have changed or 0 if no value exists.
- */
- public static long getLastShowsRatedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_SHOWS_RATED_AT, 0);
- }
-
- /**
- * The last time watched flags for episodes have changed.
- */
- public static long getLastEpisodesWatchedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_EPISODES_WATCHED_AT, 0);
- }
-
- /**
- * The last time collected flags for episodes have changed.
- */
- public static long getLastEpisodesCollectedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_EPISODES_COLLECTED_AT, 0);
- }
-
- /**
- * The last time episode ratings have changed or 0 if no value exists.
- */
- public static long getLastEpisodesRatedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_EPISODES_RATED_AT, 0);
- }
-
- /**
- * The last time watched flags for movies have changed.
- */
- public static long getLastMoviesWatchlistedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_MOVIES_WATCHLISTED_AT, 0);
- }
-
- /**
- * The last time collected flags for movies have changed.
- */
- public static long getLastMoviesCollectedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_MOVIES_COLLECTED_AT, 0);
- }
-
- /**
- * The last time movie ratings have changed or 0 if no value exists.
- */
- public static long getLastMoviesRatedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_MOVIES_RATED_AT, 0);
- }
-
- /**
- * The last time movie watched flags have changed or 0 if no value exists.
- */
- public static long getLastMoviesWatchedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_MOVIES_WATCHED_AT, 0);
- }
-
- /**
- * If either collection, watchlist or watched list have changes newer than last stored.
- */
- public static boolean isMovieListsChanged(
- Context context,
- OffsetDateTime collectedAt,
- OffsetDateTime watchlistedAt,
- OffsetDateTime watchedAt
- ) {
- return TimeTools.isAfterMillis(collectedAt, getLastMoviesCollectedAt(context))
- || TimeTools.isAfterMillis(watchlistedAt, getLastMoviesWatchlistedAt(context))
- || TimeTools.isAfterMillis(watchedAt, getLastMoviesWatchedAt(context));
- }
-
- /**
- * Store last collected, watchlisted and watched timestamps.
- */
- public static void storeLastMoviesChangedAt(
- Context context,
- OffsetDateTime collectedAt,
- OffsetDateTime watchlistedAt,
- OffsetDateTime watchedAt
- ) {
- PreferenceManager.getDefaultSharedPreferences(context)
- .edit()
- .putLong(TraktSettings.KEY_LAST_MOVIES_COLLECTED_AT,
- collectedAt.toInstant().toEpochMilli())
- .putLong(TraktSettings.KEY_LAST_MOVIES_WATCHLISTED_AT,
- watchlistedAt.toInstant().toEpochMilli())
- .putLong(TraktSettings.KEY_LAST_MOVIES_WATCHED_AT,
- watchedAt.toInstant().toEpochMilli())
- .apply();
- }
-
- /**
- * Reset {@link #KEY_LAST_MOVIES_RATED_AT} to 0 so all movie ratings will be downloaded the next
- * time a sync runs.
- */
- public static boolean resetMoviesLastRatedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .edit()
- .putLong(TraktSettings.KEY_LAST_MOVIES_RATED_AT, 0)
- .commit();
- }
-
- /**
- * Remove {@link #KEY_LAST_MOVIES_WATCHED_AT} so all watched movies will be downloaded the
- * next time a sync runs.
- */
- public static boolean resetMoviesLastWatchedAt(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .edit()
- .remove(KEY_LAST_MOVIES_WATCHED_AT)
- .commit();
- }
-
- public static boolean isAutoAddingShows(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(KEY_AUTO_ADD_TRAKT_SHOWS, true);
- }
-
- /**
- * Whether watched and collected episodes were merged with the users trakt profile since she
- * connected to trakt.
- */
- public static boolean hasMergedEpisodes(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(KEY_HAS_MERGED_EPISODES, true);
- }
-
- /**
- * Whether the list of movies was merged with the users trakt profile since she connected to
- * trakt.
- */
- public static boolean hasMergedMovies(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(KEY_HAS_MERGED_MOVIES, false);
- }
-
- /**
- * Determines if enough time has passed since the last full trakt episode sync.
- */
- public static boolean isTimeForFullEpisodeSync(Context context, long currentTime) {
- long previousUpdateTime = PreferenceManager.getDefaultSharedPreferences(context)
- .getLong(KEY_LAST_FULL_EPISODE_SYNC, currentTime);
- return (currentTime - previousUpdateTime) > FULL_SYNC_INTERVAL_MILLIS;
- }
-
- /**
- * Whether the check-in dialog should not wait for the user to enter a message, but immediately
- * start the check-in.
- */
- public static boolean useQuickCheckin(Context context) {
- return PreferenceManager.getDefaultSharedPreferences(context)
- .getBoolean(KEY_QUICK_CHECKIN, false);
- }
-}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktSettings.kt b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktSettings.kt
new file mode 100644
index 0000000000..2076ccad75
--- /dev/null
+++ b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktSettings.kt
@@ -0,0 +1,290 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2013-2024 Uwe Trottmann
+
+package com.battlelancer.seriesguide.traktapi
+
+import android.content.Context
+import androidx.core.content.edit
+import androidx.preference.PreferenceManager
+import com.battlelancer.seriesguide.util.TimeTools
+import org.threeten.bp.OffsetDateTime
+
+/**
+ * Settings related to Trakt integration.
+ */
+object TraktSettings {
+
+ /**
+ * Unused, but kept for reference.
+ *
+ * Replaced by type specific last activity timestamps.
+ */
+ private const val KEY_LAST_ACTIVITY_DOWNLOAD
+ : String = "com.battlelancer.seriesguide.lasttraktupdate"
+
+ const val KEY_LAST_SHOWS_RATED_AT
+ : String = "trakt.last_activity.shows.rated"
+
+ const val KEY_LAST_EPISODES_WATCHED_AT
+ : String = "trakt.last_activity.episodes.watched"
+
+ const val KEY_LAST_EPISODES_COLLECTED_AT
+ : String = "trakt.last_activity.episodes.collected"
+
+ const val KEY_LAST_EPISODES_RATED_AT
+ : String = "trakt.last_activity.episodes.rated"
+
+ private const val KEY_LAST_MOVIES_WATCHLISTED_AT
+ : String = "trakt.last_activity.movies.watchlisted"
+
+ private const val KEY_LAST_MOVIES_COLLECTED_AT
+ : String = "trakt.last_activity.movies.collected"
+
+ const val KEY_LAST_MOVIES_RATED_AT
+ : String = "trakt.last_activity.movies.rated"
+
+ private const val KEY_LAST_MOVIES_WATCHED_AT
+ : String = "trakt.last_activity.movies.watched"
+
+ private const val KEY_LAST_NOTES_UPDATED_AT
+ : String = "trakt.last_activity.notes.updated"
+
+ /**
+ * Unused, but kept for reference.
+ *
+ * Replaced by [KEY_LAST_EPISODES_WATCHED_AT] and [KEY_LAST_EPISODES_COLLECTED_AT].
+ */
+ private const val KEY_LAST_FULL_EPISODE_SYNC
+ : String = "com.battlelancer.seriesguide.trakt.lastfullsync"
+
+ /**
+ * Unused, but kept for reference.
+ */
+ private const val KEY_AUTO_ADD_TRAKT_SHOWS
+ : String = "com.battlelancer.seriesguide.autoaddtraktshows"
+
+ private const val KEY_HAS_MERGED_EPISODES
+ : String = "com.battlelancer.seriesguide.trakt.mergedepisodes"
+
+ private const val KEY_HAS_MERGED_MOVIES
+ : String = "com.battlelancer.seriesguide.trakt.mergedmovies"
+
+ private const val KEY_HAS_MERGED_SHOW_NOTES
+ : String = "trakt.notes.shows.merged"
+
+ /**
+ * Used in settings_basic.xml.
+ */
+ private const val KEY_QUICK_CHECKIN
+ : String = "com.battlelancer.seriesguide.trakt.quickcheckin"
+
+ /**
+ * The last time show ratings have changed or 0 if no value exists.
+ */
+ fun getLastShowsRatedAt(context: Context): Long {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_SHOWS_RATED_AT, 0)
+ }
+
+ /**
+ * The last time watched flags for episodes have changed.
+ */
+ fun getLastEpisodesWatchedAt(context: Context): Long {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_EPISODES_WATCHED_AT, 0)
+ }
+
+ /**
+ * The last time collected flags for episodes have changed.
+ */
+ fun getLastEpisodesCollectedAt(context: Context): Long {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_EPISODES_COLLECTED_AT, 0)
+ }
+
+ /**
+ * The last time episode ratings have changed or 0 if no value exists.
+ */
+ fun getLastEpisodesRatedAt(context: Context): Long {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_EPISODES_RATED_AT, 0)
+ }
+
+ /**
+ * The last time watched flags for movies have changed.
+ */
+ fun getLastMoviesWatchlistedAt(context: Context): Long {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_MOVIES_WATCHLISTED_AT, 0)
+ }
+
+ /**
+ * The last time collected flags for movies have changed.
+ */
+ fun getLastMoviesCollectedAt(context: Context): Long {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_MOVIES_COLLECTED_AT, 0)
+ }
+
+ /**
+ * The last time movie ratings have changed or 0 if no value exists.
+ */
+ fun getLastMoviesRatedAt(context: Context): Long {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_MOVIES_RATED_AT, 0)
+ }
+
+ /**
+ * The last time movie watched flags have changed or 0 if no value exists.
+ */
+ fun getLastMoviesWatchedAt(context: Context): Long {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_MOVIES_WATCHED_AT, 0)
+ }
+
+ /**
+ * The last time notes were updated or 0 if no value exists.
+ */
+ fun getLastNotesUpdatedAt(context: Context): Long {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getLong(KEY_LAST_NOTES_UPDATED_AT, 0)
+ }
+
+ fun storeLastNotesUpdatedAt(context: Context, updatedAt: OffsetDateTime) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit {
+ putLong(KEY_LAST_NOTES_UPDATED_AT, updatedAt.toInstant().toEpochMilli())
+ }
+ }
+
+ /**
+ * If either collection, watchlist or watched list have changes newer than last stored.
+ */
+ fun isMovieListsChanged(
+ context: Context,
+ collectedAt: OffsetDateTime,
+ watchlistedAt: OffsetDateTime,
+ watchedAt: OffsetDateTime
+ ): Boolean {
+ return (TimeTools.isAfterMillis(collectedAt, getLastMoviesCollectedAt(context))
+ || TimeTools.isAfterMillis(watchlistedAt, getLastMoviesWatchlistedAt(context))
+ || TimeTools.isAfterMillis(watchedAt, getLastMoviesWatchedAt(context)))
+ }
+
+ /**
+ * Store last collected, watchlisted and watched timestamps.
+ */
+ fun storeLastMoviesChangedAt(
+ context: Context,
+ collectedAt: OffsetDateTime,
+ watchlistedAt: OffsetDateTime,
+ watchedAt: OffsetDateTime
+ ) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putLong(
+ KEY_LAST_MOVIES_COLLECTED_AT,
+ collectedAt.toInstant().toEpochMilli()
+ )
+ .putLong(
+ KEY_LAST_MOVIES_WATCHLISTED_AT,
+ watchlistedAt.toInstant().toEpochMilli()
+ )
+ .putLong(
+ KEY_LAST_MOVIES_WATCHED_AT,
+ watchedAt.toInstant().toEpochMilli()
+ )
+ .apply()
+ }
+
+ /**
+ * Reset [KEY_LAST_MOVIES_RATED_AT] to 0 so all movie ratings will be downloaded the next
+ * time a sync runs.
+ */
+ @JvmStatic
+ fun resetMoviesLastRatedAt(context: Context): Boolean {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .putLong(KEY_LAST_MOVIES_RATED_AT, 0)
+ .commit()
+ }
+
+ /**
+ * Remove [KEY_LAST_MOVIES_WATCHED_AT] so all watched movies will be downloaded the
+ * next time a sync runs.
+ */
+ fun resetMoviesLastWatchedAt(context: Context): Boolean {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .remove(KEY_LAST_MOVIES_WATCHED_AT)
+ .commit()
+ }
+
+ /**
+ * Returns if episodes have not been synced with the current Trakt account.
+ */
+ fun isInitialSyncEpisodes(context: Context): Boolean {
+ return !PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(KEY_HAS_MERGED_EPISODES, false)
+ }
+
+ fun setInitialSyncEpisodesCompleted(context: Context) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit {
+ putBoolean(KEY_HAS_MERGED_EPISODES, true)
+ }
+ }
+
+ /**
+ * Returns if movies have not been synced with the current Trakt account.
+ */
+ fun isInitialSyncMovies(context: Context): Boolean {
+ return !PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(KEY_HAS_MERGED_MOVIES, false)
+ }
+
+ fun setInitialSyncMoviesCompleted(context: Context) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit {
+ putBoolean(KEY_HAS_MERGED_MOVIES, true)
+ }
+ }
+
+ /**
+ * Returns if show notes have not been synced with the current Trakt account.
+ */
+ fun isInitialSyncShowNotes(context: Context): Boolean {
+ return !PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(KEY_HAS_MERGED_SHOW_NOTES, false)
+ }
+
+ fun setInitialSyncShowNotesCompleted(context: Context) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit {
+ putBoolean(KEY_HAS_MERGED_SHOW_NOTES, true)
+ }
+ }
+
+
+ fun resetToInitialSync(context: Context) {
+ PreferenceManager.getDefaultSharedPreferences(context).edit {
+ putBoolean(KEY_HAS_MERGED_EPISODES, false)
+ putBoolean(KEY_HAS_MERGED_MOVIES, false)
+ putBoolean(KEY_HAS_MERGED_SHOW_NOTES, false)
+ // Not actually necessary, but also reset timestamps for episodes and movies
+ putLong(KEY_LAST_EPISODES_WATCHED_AT, 0)
+ putLong(KEY_LAST_EPISODES_COLLECTED_AT, 0)
+ putLong(KEY_LAST_MOVIES_WATCHED_AT, 0)
+
+ // Reset timestamps for ratings so they are downloaded immediately
+ putLong(KEY_LAST_SHOWS_RATED_AT, 0)
+ putLong(KEY_LAST_EPISODES_RATED_AT, 0)
+ putLong(KEY_LAST_MOVIES_RATED_AT, 0)
+ }
+ }
+
+ /**
+ * Whether the check-in dialog should not wait for the user to enter a message, but immediately
+ * start the check-in.
+ */
+ fun useQuickCheckin(context: Context): Boolean {
+ return PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(KEY_QUICK_CHECKIN, false)
+ }
+}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktTools2.kt b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktTools2.kt
index f2bfd1b731..17a7a27412 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktTools2.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/traktapi/TraktTools2.kt
@@ -1,11 +1,10 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2021-2024 Uwe Trottmann
package com.battlelancer.seriesguide.traktapi
import android.content.Context
import com.battlelancer.seriesguide.SgApp
-import com.battlelancer.seriesguide.shows.tools.AddUpdateShowTools.ShowResult
import com.battlelancer.seriesguide.util.Errors
import com.battlelancer.seriesguide.util.isRetryError
import com.github.michaelbull.result.Err
@@ -14,20 +13,126 @@ import com.github.michaelbull.result.Result
import com.github.michaelbull.result.andThen
import com.github.michaelbull.result.mapError
import com.github.michaelbull.result.runCatching
+import com.uwetrottmann.trakt5.TraktV2
+import com.uwetrottmann.trakt5.entities.AddNoteRequest
import com.uwetrottmann.trakt5.entities.BaseShow
-import com.uwetrottmann.trakt5.entities.LastActivities
import com.uwetrottmann.trakt5.entities.LastActivity
import com.uwetrottmann.trakt5.entities.LastActivityMore
+import com.uwetrottmann.trakt5.entities.LastActivityUpdated
+import com.uwetrottmann.trakt5.entities.Note
import com.uwetrottmann.trakt5.entities.Ratings
import com.uwetrottmann.trakt5.entities.Show
+import com.uwetrottmann.trakt5.entities.ShowIds
import com.uwetrottmann.trakt5.enums.Extended
import com.uwetrottmann.trakt5.enums.IdType
import com.uwetrottmann.trakt5.enums.Type
+import com.uwetrottmann.trakt5.services.Notes
+import retrofit2.Call
import retrofit2.Response
+import retrofit2.awaitResponse
object TraktTools2 {
- data class SearchResult(val result: ShowResult, val show: Show?)
+ sealed interface TraktResponse {
+ data class Success(
+ /**
+ * If T is [Void] this is always `null`.
+ */
+ val data: T?
+ ) : TraktResponse
+ }
+
+ sealed interface TraktNonNullResponse {
+ data class Success(val data: T) : TraktNonNullResponse
+ }
+
+ sealed interface TraktErrorResponse {
+ class IsNotVip : TraktResponse, TraktNonNullResponse
+ class IsUnauthorized : TraktResponse, TraktNonNullResponse
+ class Other : TraktResponse, TraktNonNullResponse
+ }
+
+ /**
+ * Adds or updates the note for the given show.
+ */
+ suspend fun saveNoteForShow(
+ traktNotes: Notes,
+ showTmdbId: Int,
+ noteText: String
+ ): TraktNonNullResponse {
+ // Note: calling the add endpoint for an existing note will update it
+ return awaitTraktCallNonNull(
+ traktNotes.addNote(
+ AddNoteRequest(
+ Show().apply {
+ ids = ShowIds.tmdb(showTmdbId)
+ },
+ noteText
+ )
+ ), "update note"
+ )
+ }
+
+ suspend fun deleteNote(
+ trakt: SgTrakt,
+ noteId: Long
+ ): TraktResponse {
+ return awaitTraktCall(trakt.notes().deleteNote(noteId), "delete note")
+ }
+
+ private suspend fun awaitTraktCall(
+ call: Call,
+ action: String,
+ logErrorOnNullBody: Boolean = false
+ ): TraktResponse {
+ val response = try {
+ call.awaitResponse()
+ } catch (e: Exception) {
+ Errors.logAndReport(action, e)
+ return TraktErrorResponse.Other()
+ }
+
+ if (!response.isSuccessful) {
+ return when {
+ TraktV2.isNotVip(response) -> TraktErrorResponse.IsNotVip()
+ TraktV2.isUnauthorized(response) -> TraktErrorResponse.IsUnauthorized()
+ else -> {
+ Errors.logAndReport(action, response)
+ TraktErrorResponse.Other()
+ }
+ }
+ }
+
+ val body = response.body()
+
+ if (logErrorOnNullBody && body == null) {
+ Errors.logAndReport(action, response, "body is null")
+ }
+
+ return TraktResponse.Success(body)
+ }
+
+ /**
+ * Like [awaitTraktCall], but ensures the response data is not null.
+ */
+ private suspend fun awaitTraktCallNonNull(
+ call: Call,
+ action: String
+ ): TraktNonNullResponse {
+ return when (val response = awaitTraktCall(call, action, logErrorOnNullBody = true)) {
+ is TraktErrorResponse.Other -> response
+ is TraktErrorResponse.IsNotVip -> response
+ is TraktErrorResponse.IsUnauthorized -> response
+ is TraktResponse.Success -> {
+ val data = response.data
+ if (data == null) {
+ TraktErrorResponse.Other()
+ } else {
+ TraktNonNullResponse.Success(data)
+ }
+ }
+ }
+ }
/**
* Look up a show by its TMDB ID, may return `null` if not found.
@@ -140,6 +245,7 @@ object TraktTools2 {
val episodes: LastActivityMore,
val shows: LastActivity,
val movies: LastActivityMore,
+ val notes: LastActivityUpdated,
)
fun getLastActivity(context: Context): Result {
@@ -157,12 +263,14 @@ object TraktTools2 {
val episodes = lastActivities?.episodes
val shows = lastActivities?.shows
val movies = lastActivities?.movies
- if (episodes != null && shows != null && movies != null) {
+ val notes = lastActivities?.notes
+ if (episodes != null && shows != null && movies != null && notes != null) {
return@andThen Ok(
LastActivities(
episodes = episodes,
shows = shows,
- movies = movies
+ movies = movies,
+ notes = notes
)
)
} else {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/ui/BaseThemeActivity.kt b/app/src/main/java/com/battlelancer/seriesguide/ui/BaseThemeActivity.kt
index ca4321b2bd..088290809d 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/ui/BaseThemeActivity.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/ui/BaseThemeActivity.kt
@@ -51,7 +51,7 @@ abstract class BaseThemeActivity : AppCompatActivity() {
android.R.id.home -> {
val upIntent = NavUtils.getParentActivityIntent(this)
if (upIntent != null) {
- if (NavUtils.shouldUpRecreateTask(this, upIntent)) {
+ if (shouldUpRecreateTask(upIntent)) {
// This activity is NOT part of this app's task, so create a new task
// when navigating up, with a synthesized back stack.
TaskStackBuilder.create(this)
@@ -62,7 +62,7 @@ abstract class BaseThemeActivity : AppCompatActivity() {
} else {
// This activity is part of this app's task, so simply
// navigate up to the logical parent activity.
- NavUtils.navigateUpTo(this, upIntent)
+ navigateUpTo(upIntent)
}
} else {
// No parent activity defined in AndroidManifest, let back press handle up
diff --git a/app/src/main/java/com/battlelancer/seriesguide/util/ActivityTools.kt b/app/src/main/java/com/battlelancer/seriesguide/util/ActivityTools.kt
index 9ee77b1d35..3a3b625c21 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/util/ActivityTools.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/util/ActivityTools.kt
@@ -1,10 +1,13 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2014-2024 Uwe Trottmann
package com.battlelancer.seriesguide.util
+import android.app.ActivityOptions
import android.content.ActivityNotFoundException
import android.content.Context
+import android.content.Intent
+import android.view.View
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import com.battlelancer.seriesguide.R
@@ -19,4 +22,16 @@ fun ActivityResultLauncher.tryLaunch(input: I, context: Context) {
} catch (e: ActivityNotFoundException) {
Toast.makeText(context, R.string.app_not_available, Toast.LENGTH_LONG).show()
}
+}
+
+/**
+ * Uses [ActivityOptions.makeScaleUpAnimation] on the [sourceView].
+ */
+fun Context.startActivityWithAnimation(intent: Intent, sourceView: View) {
+ startActivity(
+ intent,
+ ActivityOptions
+ .makeScaleUpAnimation(sourceView, 0, 0, sourceView.width, sourceView.height)
+ .toBundle()
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/battlelancer/seriesguide/util/ImageTools.kt b/app/src/main/java/com/battlelancer/seriesguide/util/ImageTools.kt
index 72780f0ff6..00adfbdfd5 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/util/ImageTools.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/util/ImageTools.kt
@@ -1,5 +1,5 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2021-2024 Uwe Trottmann
package com.battlelancer.seriesguide.util
@@ -71,45 +71,24 @@ object ImageTools {
return tmdbOrTvdbPosterUrl(imagePath, context)
}
+ /**
+ * Calls [buildTmdbOrTvdbImageCacheUrl] with small image and demo URLs for show posters.
+ */
@JvmStatic
fun tmdbOrTvdbPosterUrl(
imagePath: String?,
context: Context,
originalSize: Boolean = false
): String? {
- return if (imagePath.isNullOrEmpty()) {
- null
- } else {
- if (AppSettings.isDemoModeEnabled(context)) {
- return pickDemoPosterUrl(imagePath)
+ return buildTmdbOrTvdbImageCacheUrl(
+ imagePath, context, originalSize,
+ demoUrl = { nonNullImagePath ->
+ pickDemoPosterUrl(nonNullImagePath)
+ },
+ tmdbSmallImageUrl = { nonNullImagePath ->
+ "${TmdbSettings.getPosterBaseUrl(context)}$nonNullImagePath"
}
-
- // If the path contains the legacy TVDB cache prefix, use the www subdomain as it has
- // a redirect to the new thumbnail URL set up (artworks subdomain + file name postfix).
- // E.g. https://www.thetvdb.com/banners/_cache/posters/example.jpg redirects to
- // https://artworks.thetvdb.com/banners/posters/example_t.jpg
- // Using the artworks subdomain with the legacy cache prefix is not supported.
- val imageUrl = when {
- imagePath.contains(TVDB_LEGACY_CACHE_PREFIX, false) -> {
- "${TVDB_LEGACY_MIRROR_BANNERS}$imagePath"
- }
-
- imagePath.startsWith("/") -> {
- // TMDB images have no path at all, but always start with /.
- // Use small size based on density, or original size (as large as possible).
- if (originalSize) {
- TmdbSettings.getImageOriginalUrl(context, imagePath)
- } else {
- "${TmdbSettings.getPosterBaseUrl(context)}$imagePath"
- }
- }
-
- else -> {
- "${TVDB_MIRROR_BANNERS}$imagePath"
- }
- }
- buildImageCacheUrl(imageUrl)
- }
+ )
}
private val demoPosterUrls = listOf(
@@ -123,24 +102,50 @@ object ImageTools {
"https://seriesgui.de/demo/sitcom.jpg",
)
- private val demoStillUrl = "https://seriesgui.de/demo/episode-anime.jpg"
+ private val demoEpisodeImageUrl = "https://seriesgui.de/demo/episode-anime.jpg"
private fun pickDemoPosterUrl(imagePath: String): String {
// Map an image path always to the same image
return demoPosterUrls[imagePath.hashCode().mod(demoPosterUrls.size)]
}
- @JvmStatic
- fun tmdbOrTvdbStillUrl(
+ /**
+ * Calls [buildTmdbOrTvdbImageCacheUrl] with small image and demo URLs for episode images.
+ */
+ fun buildEpisodeImageUrl(
imagePath: String?,
context: Context,
originalSize: Boolean = false
+ ): String? {
+ return buildTmdbOrTvdbImageCacheUrl(
+ imagePath, context, originalSize,
+ demoUrl = { demoEpisodeImageUrl },
+ tmdbSmallImageUrl = { nonNullImagePath ->
+ TmdbSettings.buildBackdropUrl(context, nonNullImagePath)
+ }
+ )
+ }
+
+ /**
+ * Builds an image cache URL, or returns null if [imagePath] is null or empty.
+ *
+ * Returns [demoUrl] if [AppSettings.isDemoModeEnabled] is enabled.
+ *
+ * If [imagePath] starts with `/` builds a TMDB episode image path with resolution depending on
+ * [originalSize]. Otherwise a legacy TVDB URL.
+ */
+ private fun buildTmdbOrTvdbImageCacheUrl(
+ imagePath: String?,
+ context: Context,
+ originalSize: Boolean = false,
+ demoUrl: (String) -> String,
+ tmdbSmallImageUrl: (String) -> String
): String? {
return if (imagePath.isNullOrEmpty()) {
null
} else {
if (AppSettings.isDemoModeEnabled(context)) {
- return demoStillUrl
+ return demoUrl(imagePath)
}
// If the path contains the legacy TVDB cache prefix, use the www subdomain as it has
@@ -159,7 +164,7 @@ object ImageTools {
if (originalSize) {
TmdbSettings.getImageOriginalUrl(context, imagePath)
} else {
- TmdbSettings.getStillUrl(context, imagePath)
+ tmdbSmallImageUrl(imagePath)
}
}
@@ -174,7 +179,7 @@ object ImageTools {
/**
* [posterUrl] must not be empty.
*/
- fun buildImageCacheUrl(posterUrl: String): String? {
+ private fun buildImageCacheUrl(posterUrl: String): String? {
@Suppress("SENSELESS_COMPARISON")
if (BuildConfig.IMAGE_CACHE_URL == null) {
return posterUrl // no cache
diff --git a/app/src/main/java/com/battlelancer/seriesguide/util/PackageTools.kt b/app/src/main/java/com/battlelancer/seriesguide/util/PackageTools.kt
index 32557bbdf4..43f04547bd 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/util/PackageTools.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/util/PackageTools.kt
@@ -1,5 +1,5 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2023-2024 Uwe Trottmann
package com.battlelancer.seriesguide.util
@@ -26,11 +26,12 @@ object PackageTools {
* Get version name from this apps package.
*/
fun getVersion(context: Context): String {
- return try {
+ val version = try {
getAppPackage(context).versionName
} catch (e: PackageManager.NameNotFoundException) {
- "UnknownVersion"
+ null
}
+ return version ?: "UnknownVersion"
}
/**
@@ -72,7 +73,8 @@ object PackageTools {
)
val sgSignatures = appInfoSeriesGuide.signatures
val xSignatures = appInfoSeriesGuideX.signatures
- if (sgSignatures.size == xSignatures.size) {
+ if (sgSignatures != null && xSignatures != null
+ && sgSignatures.size == xSignatures.size) {
for (i in sgSignatures.indices) {
if (sgSignatures[i].toCharsString() != xSignatures[i].toCharsString()) {
return false // a signature does not match
diff --git a/app/src/main/java/com/battlelancer/seriesguide/util/ThemeUtils.kt b/app/src/main/java/com/battlelancer/seriesguide/util/ThemeUtils.kt
index 565eff9a76..9fdc532018 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/util/ThemeUtils.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/util/ThemeUtils.kt
@@ -1,5 +1,5 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2015-2024 Uwe Trottmann
package com.battlelancer.seriesguide.util
@@ -167,7 +167,7 @@ object ThemeUtils {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// A light status bar is only supported on M+.
// Use a translucent black status bar instead.
- val opaqueStatusBarColor: Int =
+ @Suppress("DEPRECATION") val opaqueStatusBarColor: Int =
MaterialColors.getColor(context, android.R.attr.statusBarColor, Color.BLACK)
ColorUtils.setAlphaComponent(opaqueStatusBarColor, EDGE_TO_EDGE_BAR_ALPHA)
} else {
@@ -179,7 +179,7 @@ object ThemeUtils {
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
// A light navigation bar is only supported on O_MR1+.
// Use a translucent black navigation bar instead.
- val opaqueNavBarColor =
+ @Suppress("DEPRECATION") val opaqueNavBarColor =
MaterialColors.getColor(context, android.R.attr.navigationBarColor, Color.BLACK)
ColorUtils.setAlphaComponent(opaqueNavBarColor, EDGE_TO_EDGE_BAR_ALPHA)
} else {
diff --git a/app/src/main/java/com/battlelancer/seriesguide/util/TimeTools.kt b/app/src/main/java/com/battlelancer/seriesguide/util/TimeTools.kt
index c3c79ae20c..12f813f3a5 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/util/TimeTools.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/util/TimeTools.kt
@@ -64,7 +64,6 @@ object TimeTools {
return dateTime.toInstant().isBefore(Instant.ofEpochMilli(millis))
}
- @JvmStatic
fun isAfterMillis(dateTime: OffsetDateTime, millis: Long): Boolean {
return dateTime.toInstant().isAfter(Instant.ofEpochMilli(millis))
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/util/Utils.java b/app/src/main/java/com/battlelancer/seriesguide/util/Utils.java
index e9bbcaf18e..f64edd6576 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/util/Utils.java
+++ b/app/src/main/java/com/battlelancer/seriesguide/util/Utils.java
@@ -1,5 +1,5 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2011-2024 Uwe Trottmann
package com.battlelancer.seriesguide.util;
@@ -8,11 +8,8 @@
import android.content.Context;
import android.content.Intent;
import android.util.Log;
-import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
-import androidx.core.app.ActivityCompat;
-import androidx.core.app.ActivityOptionsCompat;
import androidx.fragment.app.Fragment;
import com.battlelancer.seriesguide.BuildConfig;
import com.battlelancer.seriesguide.R;
@@ -62,8 +59,8 @@ public static boolean hasXpass(Context context) {
}
/**
- * Launches {@link com.battlelancer.seriesguide.billing.amazon.AmazonBillingActivity} or {@link
- * BillingActivity} and notifies that something is only available with the subscription.
+ * Launches {@link com.battlelancer.seriesguide.billing.amazon.AmazonBillingActivity} or
+ * {@link BillingActivity} and notifies that something is only available with the subscription.
*/
public static void advertiseSubscription(Context context) {
Toast.makeText(context, R.string.onlyx, Toast.LENGTH_SHORT).show();
@@ -81,15 +78,15 @@ public static boolean isAmazonVersion() {
@NonNull
public static Intent getBillingActivityIntent(Context context) {
if (Utils.isAmazonVersion()) {
- return new Intent(context, AmazonBillingActivity.class);
+ return new Intent(context, AmazonBillingActivity.class);
} else {
return new Intent(context, BillingActivity.class);
}
}
/**
- * Returns false if there is an active, but metered connection and
- * the user did not approve it for large data downloads (e.g. images).
+ * Returns false if there is an active, but metered connection and the user did not approve it
+ * for large data downloads (e.g. images).
*/
static boolean isAllowedLargeDataConnection(Context context) {
boolean isConnected;
@@ -174,14 +171,6 @@ public static void tryStartActivityForResult(Fragment fragment, Intent intent,
}
}
- public static void startActivityWithAnimation(Context context, Intent intent, View view) {
- ActivityCompat.startActivity(context, intent,
- ActivityOptionsCompat
- .makeScaleUpAnimation(view, 0, 0, view.getWidth(), view.getHeight())
- .toBundle()
- );
- }
-
/**
* Tries to start the given intent as a new document (e.g. opening a website, other app) so it
* appears as a new entry in the task switcher using {@link #tryStartActivity}.
@@ -191,5 +180,4 @@ public static boolean openNewDocument(@NonNull Context context, @NonNull Intent
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
return Utils.tryStartActivity(context, intent, true);
}
-
}
diff --git a/app/src/main/java/com/battlelancer/seriesguide/util/ViewTools.kt b/app/src/main/java/com/battlelancer/seriesguide/util/ViewTools.kt
index 6ad4a7c12f..8898410032 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/util/ViewTools.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/util/ViewTools.kt
@@ -19,6 +19,7 @@ import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.battlelancer.seriesguide.R
+import java.text.NumberFormat
object ViewTools {
@@ -86,7 +87,7 @@ object ViewTools {
return if (value != null && value > 0.0) {
label.visibility = View.VISIBLE
text.visibility = View.VISIBLE
- text.text = value.toString()
+ text.text = NumberFormat.getNumberInstance().format(value)
true
} else {
label.visibility = View.GONE
diff --git a/app/src/main/java/com/battlelancer/seriesguide/util/WebTools.kt b/app/src/main/java/com/battlelancer/seriesguide/util/WebTools.kt
index 8656fbd57a..af8900495c 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/util/WebTools.kt
+++ b/app/src/main/java/com/battlelancer/seriesguide/util/WebTools.kt
@@ -1,5 +1,5 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2023-2024 Uwe Trottmann
package com.battlelancer.seriesguide.util
@@ -22,7 +22,6 @@ object WebTools {
*
* See also [openInApp].
*/
- @JvmStatic
fun openInCustomTab(context: Context, url: String): Boolean {
val darkParams = CustomTabColorSchemeParams.Builder()
.setToolbarColor(
diff --git a/app/src/main/java/com/battlelancer/seriesguide/util/tasks/RemoveListTask.java b/app/src/main/java/com/battlelancer/seriesguide/util/tasks/DeleteListTask.java
similarity index 93%
rename from app/src/main/java/com/battlelancer/seriesguide/util/tasks/RemoveListTask.java
rename to app/src/main/java/com/battlelancer/seriesguide/util/tasks/DeleteListTask.java
index 54298e78da..3e06e24d1d 100644
--- a/app/src/main/java/com/battlelancer/seriesguide/util/tasks/RemoveListTask.java
+++ b/app/src/main/java/com/battlelancer/seriesguide/util/tasks/DeleteListTask.java
@@ -14,13 +14,13 @@
import java.io.IOException;
/**
- * Task to remove a list.
+ * Task to delete a list and its items.
*/
-public class RemoveListTask extends BaseActionTask {
+public class DeleteListTask extends BaseActionTask {
@NonNull protected final String listId;
- public RemoveListTask(@NonNull Context context, @NonNull String listId) {
+ public DeleteListTask(@NonNull Context context, @NonNull String listId) {
super(context);
this.listId = listId;
}
diff --git a/app/src/main/res/drawable/ic_calendar_month_control_24dp.xml b/app/src/main/res/drawable/ic_calendar_month_control_24dp.xml
new file mode 100644
index 0000000000..a68f64c89c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calendar_month_control_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_edit_note_control_24dp.xml b/app/src/main/res/drawable/ic_edit_note_control_24dp.xml
new file mode 100644
index 0000000000..7299f4ee80
--- /dev/null
+++ b/app/src/main/res/drawable/ic_edit_note_control_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_event_control_24dp.xml b/app/src/main/res/drawable/ic_event_control_24dp.xml
new file mode 100644
index 0000000000..8cf9596eca
--- /dev/null
+++ b/app/src/main/res/drawable/ic_event_control_24dp.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_event_white_24dp.xml b/app/src/main/res/drawable/ic_event_white_24dp.xml
deleted file mode 100644
index e5d971795a..0000000000
--- a/app/src/main/res/drawable/ic_event_white_24dp.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_shows.xml b/app/src/main/res/layout/activity_shows.xml
index fac7843cd4..cc27d80e6b 100644
--- a/app/src/main/res/layout/activity_shows.xml
+++ b/app/src/main/res/layout/activity_shows.xml
@@ -21,7 +21,7 @@
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
diff --git a/app/src/main/res/layout/buttons_episode.xml b/app/src/main/res/layout/buttons_episode.xml
index 994a179ed6..0e96689451 100644
--- a/app/src/main/res/layout/buttons_episode.xml
+++ b/app/src/main/res/layout/buttons_episode.xml
@@ -107,7 +107,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/addtocalendar"
- app:icon="@drawable/ic_event_white_24dp"
+ app:icon="@drawable/ic_event_control_24dp"
app:iconGravity="start" />
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_custom_release_time.xml b/app/src/main/res/layout/dialog_custom_release_time.xml
index 5fee7ab619..c6890657d6 100644
--- a/app/src/main/res/layout/dialog_custom_release_time.xml
+++ b/app/src/main/res/layout/dialog_custom_release_time.xml
@@ -34,7 +34,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@null"
- android:src="@drawable/ic_schedule_black_24dp" />
+ app:srcCompat="@drawable/ic_schedule_black_24dp" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_list_manage.xml b/app/src/main/res/layout/dialog_list_manage.xml
index 368a0daad9..652eb287be 100644
--- a/app/src/main/res/layout/dialog_list_manage.xml
+++ b/app/src/main/res/layout/dialog_list_manage.xml
@@ -15,7 +15,8 @@
+ android:layout_height="wrap_content"
+ app:endIconMode="clear_text">
@@ -38,12 +39,13 @@
android:paddingRight="@dimen/large_padding"
android:paddingBottom="@dimen/inline_padding">
+
+ android:background="?attr/sgColorDivider"
+ app:tabMode="scrollable">
+ android:icon="@drawable/ic_filter_white_24dp"
+ android:text="@string/title_filter_general" />
+ android:icon="@drawable/ic_sort_white_24dp"
+ android:text="@string/sort" />
diff --git a/app/src/main/res/layout/dialog_watch_provider_filter.xml b/app/src/main/res/layout/dialog_watch_provider_filter.xml
index dc0a3c24c4..0ae15a9283 100644
--- a/app/src/main/res/layout/dialog_watch_provider_filter.xml
+++ b/app/src/main/res/layout/dialog_watch_provider_filter.xml
@@ -5,25 +5,6 @@
android:layout_height="wrap_content"
android:orientation="vertical">
-
-
-
-
+ app:srcCompat="@drawable/ic_schedule_black_24dp" />
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_show_regular.xml b/app/src/main/res/layout/fragment_show_regular.xml
index 09294ac677..005a9937ea 100644
--- a/app/src/main/res/layout/fragment_show_regular.xml
+++ b/app/src/main/res/layout/fragment_show_regular.xml
@@ -256,6 +256,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/layout_ratings.xml b/app/src/main/res/layout/layout_ratings.xml
index 199c4f86d1..5a8766a917 100644
--- a/app/src/main/res/layout/layout_ratings.xml
+++ b/app/src/main/res/layout/layout_ratings.xml
@@ -53,10 +53,10 @@
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/trakt"
- android:src="@drawable/ic_trakt_control_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@id/ratingViewTrakt" />
+ app:layout_constraintTop_toBottomOf="@id/ratingViewTrakt"
+ app:srcCompat="@drawable/ic_trakt_control_24dp" />
+ android:layout_height="wrap_content"
+ android:scrollbars="vertical">
+ app:srcCompat="@drawable/ic_settings_control_24dp" />
diff --git a/app/src/main/res/layout/view_first_run.xml b/app/src/main/res/layout/view_first_run.xml
index 1577205e36..3029e77c9d 100644
--- a/app/src/main/res/layout/view_first_run.xml
+++ b/app/src/main/res/layout/view_first_run.xml
@@ -19,9 +19,9 @@
android:layout_height="56dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/dismiss"
- android:src="@drawable/ic_clear_24dp"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintTop_toTopOf="parent"
+ app:srcCompat="@drawable/ic_clear_24dp" />
+ android:layout_height="wrap_content"
+ android:scrollbars="vertical">
-
\ No newline at end of file
diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
index d72981a238..a6fd5dd672 100644
--- a/app/src/main/res/values-ar/strings.xml
+++ b/app/src/main/res/values-ar/strings.xml
@@ -28,7 +28,6 @@
تنظيف
تجاهل
- حفظ التحديد
عرض الكل
البحث في الانترنت
إضافة إلى الشاشة الرئيسية
@@ -40,6 +39,7 @@
Stream or purchase settings
حدد المنطقة
Reset
+ Save
إزالة تاريخ البحث
البحث عن الحلقات
@@ -153,7 +153,8 @@
تم الإخفاء
تم إظهار المسلسل
إضافة مسلسل
- فلترة المسلسلات
+ Filter and sort
+ General
إزالة كل عوامل التصفية
المفضلة
الغير مشاهدة
@@ -164,7 +165,6 @@
مستبعدة
اظهار الجميع
هل تريد إظهار كل المسلسلات المخفية %s مرة أخرى؟
- ترتيب المسلسلات
العنوان
آخر حلقة
أقدم حلقة
@@ -176,6 +176,8 @@
تم مشاهدة الحلقة، الحلقة التالية
تغيير طريقة العرض
التالي للمشاهدة
+ Notes
+ Edit note
السجل
تم مشاهدته مؤخراً
@@ -265,8 +267,10 @@
Similar
مسلسلات مشابهة
-
+
+ استكشاف
الأكثر شعبية
+ Filter
Year
Current year
Language
@@ -278,18 +282,16 @@
إزالة القائمة
إدارة القائمة
List renamed
- List removed
+ List deleted
إدارة القوائم
Lists updated
إزالة من القائمة
Removed from list
يمكنك إضافة مسلسلات إلى هذه القائمة
- ترتيب القوائم
إعادة ترتيب القوائم
Lists reordered
يوجد بالفعل قائمة بهذا الإسم
- استكشاف
الإصدارات الرقمية
إصدارات القرص
حاليا في السينما
@@ -306,11 +308,10 @@
العرض الدعائي
طاقم التمثيل
طاقم
- فرز الأفلام
العنوان
تاريخ الإصدار
- تصفية الأفلام
Similar movies
+ Release dates
خطأ في الإحصائيات.
%s انتهى مشاهدة
@@ -432,8 +433,6 @@
\"31 أكتوبر\" بدلاً من \"في 3 أيام\"
منع المفسدين
إخفاء التفاصيل حتى هو شاهد الحلقة
- ترتيب المواسم حسب…
- ترتيب الحلقات حسب…
اللغة المفضلة
اللغة البديلة
تحميل الصور بواسطة الواي فاي فقط
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
index a818f39210..4670569c30 100644
--- a/app/src/main/res/values-bg/strings.xml
+++ b/app/src/main/res/values-bg/strings.xml
@@ -28,7 +28,6 @@
Изчисти
Откажи
- Запази избраните
Покажи всички
Търсене в мрежата
Добави към стартовия екран
@@ -37,9 +36,10 @@
Синхронизиране & актуализация
Синхронизиране & Изтегли всички
Стрийм или закупи
- Stream or purchase settings
+ Настройки за стрийм или поръчка
Избор на регион
Нулиране
+ Save
Изчисти историята на търсене
Търсене на епизоди
@@ -149,7 +149,8 @@
Скрито ТВ предаване
Показано ТВ предаване
Добавяне на тв предаване
- Филтриране на сериали
+ Filter and sort
+ General
Премахни всички филтри
Любими
Негледани
@@ -160,7 +161,6 @@
Без тези
Виж всички скрити
Направи всички скрити сериали (%s) видими?
- Сортирай сериалите
Заглавие
Последен епизод
Най-старият епизод
@@ -172,6 +172,8 @@
Гледан следващ епизод
Промени изгледа
Следващ за гледане
+ Notes
+ Edit note
История
Наскоро гледан/и
@@ -237,8 +239,10 @@
Подобни
Подобни сериали
-
+
+ Открийте
Популярни
+ Filter
Година
Текуща година
Език
@@ -256,12 +260,10 @@
Премахване от списъка
Премахнато от списъка
Добавете сериали в този списък
- Подреждане на списъка
Пренареждане на списъците
Списъците са подредени
Списък с това име вече съществува
- Открийте
Дихитални издания
Излезли на диск
В кината
@@ -278,11 +280,10 @@
Трейлър
В ролите
Екип
- Подреждане на филми
Заглавие
Дата на издаване
- Филтриране
Подобни филми
+ Release dates
Не може да се изчисли статистиката.
%s изгледани и приключени
@@ -388,8 +389,6 @@
\"31 октомври\" вместо \"след 3 дни\"
Предотвратяване на спойлери
Скрий детайлите, докато е епизода бъде гледан
- Сортирай сезоните по…
- Сортирай епизодите по…
Предпочитан език на съдържанието
Алтернативен език
Изображения само при Wi-Fi
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 8d073254c7..3ad757b9f9 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -28,7 +28,6 @@
Buida
Descarta
- Desa la selecció
Mostra-ho tot
Cerca web
Afegir a la pantalla d\'inici
@@ -40,6 +39,7 @@
Veure en línia o configuració de la compra
Selecciona la regió
Restablir
+ Desar
Esborra l\'historial
Cerca episodis
@@ -53,7 +53,7 @@
Cap aplicació disponible per gestionar això
No hi ha ningú? Toqueu per intentar-ho novament.
No s\'han pogut modificar les dades.
- Intenta fer un backup en el panell de Configuraciò, torneu a instal·lar i restaurar.
+ Intenta fer una còpia de seguretat en el panell de Configuració, torneu a instal·lar i restaurar.
No és possible contactar amb %s. Proveu-ho més tard.
Ha fallat: %s
copiat al porta-retalls
@@ -149,7 +149,8 @@
Sèrie oculta
Sèrie visible
Afegeix sèrie
- Filtra sèries
+ Filtra i ordena
+ General
Desactiva tots els filtres
Preferits
No vist
@@ -160,7 +161,6 @@
exclosos
Fes tot els ocults visibles
Fer totes les sèries amagades (%s) visibles un altre cop?
- Ordena sèries
Títol
Últim episodi
Episodi més antic
@@ -172,6 +172,8 @@
Següent episodi vist
Canvia la vista
Següent a mirar
+ Notes
+ Editar la nota
Històric
Vist Recentment
@@ -208,7 +210,7 @@
- %d restants
- - %1$d of %2$d restant
+ - %1$d de %2$d restant
- %1$d de %2$d restants
@@ -237,8 +239,10 @@
Semblants
Sèries similars
-
+
+ Descobrir
Popular
+ Filtres
Any
Any Actual
Idioma
@@ -256,12 +260,10 @@
Esborra de la llista
Eliminada de la llista
Pots afegir sèries a aquesta llista
- Ordena les llistes
Reordenar les llistes
Llistes reordenades
Ja existeix una llista amb aquest nom
- Descobrir
Publicació digital
Publicació en disc
En els cinemes
@@ -278,11 +280,10 @@
Tràiler
Repartiment
Equip
- Ordena les peŀlícules
Títol
Data de llançament
- Filtre de pel·lícules
Pel·lícules similars
+ Dates de llançament
No s\'han pogut calcular les estadístiques.
%s acabat de veure
@@ -388,8 +389,6 @@
\"31 d\'octubre\" enlloc de \"D\'aquí a 3 dies\"
Evita els spoilers
Amaga els detalls fins que l\'episodi s\'hagi vist
- Ordena les temporades per…
- Ordena els capítols per…
Idioma preferit del contingut
Idioma alternatiu
Imatges només via xarxa sense fils
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index a499e01146..455c3ca395 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -28,7 +28,6 @@
Vyčistit
Zrušit
- Uložit výběr
Zobrazit vše
Vyhledávání na webu
Přidat na základní obrazovku
@@ -40,6 +39,7 @@
Stream or purchase settings
Vyberte region
Obnovit
+ Save
Vymazat historii vyhledávání
Hledat epizody
@@ -151,7 +151,8 @@
Skrytý seriál
Obnovený seriál
Přidat seriál
- Filtrovat seriály
+ Filter and sort
+ General
Zrušit všechny filtry
Oblíbené
Nezhlédnuté
@@ -162,7 +163,6 @@
Vyloučené
Zviditelnit všechny skryté
Znovu zviditelnit všechny skryté seriály (%s)?
- Seřadit seriály
Název
Nejnovější díl
Nejstarší díl
@@ -174,6 +174,8 @@
Následujicí epizoda zhlédnuta
Přepnout zobrazení
Další ke zhlédnutí
+ Notes
+ Edit note
Historie
Nedávno zhlédnuto
@@ -251,8 +253,10 @@
Podobné
Podobné seriály
-
+
+ Objevujte
Populární
+ Filter
Rok
Tento rok
Jazyk
@@ -270,12 +274,10 @@
Odebrat ze seznamu
Odstraněno ze seznamu
Můžete přidat seriály do tohoto seznamu
- Třídit seznamy
Uspořádat seznamy
Řazení seznamů změněno
Seznam s tímto názvem již existuje
- Objevujte
Digitální vydání
Vydání na discích
V kinech
@@ -292,11 +294,10 @@
Trailer
Obsazení
Kolektiv
- Seřadit filmy
Název
Datum vysílání
- Filtrovat filmy
Podobné filmy
+ Release dates
Nelze přepočítat statistiky.
%s dokončených zhlédnutí
@@ -410,8 +411,6 @@
\"31. října\" místo \"za 3 dny\"
Zabraň spoilerům
Skrýt podrobnosti, dokud není epizoda zhlédnuta
- Řadit série dle…
- Řadit epizody dle…
Preferovaný jazyk obsahu
Alternativní jazyk
Obrázky pouze přes Wi-Fi
diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml
index 6e7a06a24b..f03adf39db 100644
--- a/app/src/main/res/values-cy/strings.xml
+++ b/app/src/main/res/values-cy/strings.xml
@@ -28,7 +28,6 @@
Clir
Gwared
- Cadw dewis
Dangos y cyfan
Chwilio\'r We
Ychwanegu i\'r sgrin gartref
@@ -40,6 +39,7 @@
Stream or purchase settings
Dewis rhanbarth
Reset
+ Save
Hanes chwilio clir
Chwilio penodau
@@ -153,7 +153,8 @@
Wedi\'i symud i gudd
Sioe i\'w gweld eto
Ychwanegu sioe
- Hidlo sioeau
+ Filter and sort
+ General
Tynnwch yr holl hidlwyr
Hoff
Heb ei wylio
@@ -164,7 +165,6 @@
Eithriedig
Gwneud pob cudd yn weladwy
Gwneud pob sioe gudd (%s) yn weladwy eto?
- Trefnu sioeau
Teitl
Y bennod ddiweddaraf
Y bennod hynaf
@@ -176,6 +176,8 @@
Wedi gwylio\'r bennod nesaf
Newid golygfa
Nesaf i wylio
+ Notes
+ Edit note
Hanes
Gwyliwyd yn ddiweddar
@@ -265,8 +267,10 @@
Similar
Sioeau tebyg
-
+
+ Darganfod
Poblogaidd
+ Filter
Year
Current year
Language
@@ -278,18 +282,16 @@
Dileu\'r rhestr
Rheoli rhestr
List renamed
- List removed
+ List deleted
Rheoli rhestrau
Lists updated
Tynnu o\'r rhestr
Removed from list
Gallwch ychwanegu sioeau at y rhestr hon
- Trefnu rhestrau
Rhestrau ail-archebu
Lists reordered
Mae rhestr gyda\'r enw hwn eisoes yn bodoli
- Darganfod
Datganiadau digidol
Datganiadau disg
Mewn sinemâu
@@ -306,11 +308,10 @@
Trelar Ffilm
Cast Ffilm
Criw Ffilm
- Trefnu ffilmiau
Teitl
Dyddiad rhyddhau
- hidlo ffilmiau
Similar movies
+ Release dates
Methu cyfrifo ystadegau
%s gorffen gwylio
@@ -432,8 +433,6 @@
\"Hydref 31\" yn lle \"mewn 3 diwrnod\"
Atal anrheithwyr
Cuddiwch fanylion nes bod pennod yn cael ei gwylio
- Trefnu tymhorau yn ôl…
- Trefnu penodau yn ôl…
Yr iaith cynnwys a ffefrir
Iaith amgen
Delweddau trwy Wi-Fi yn unig
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 35d694881c..f67238149f 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -28,7 +28,6 @@
Ryd
Kassér
- Gem markering
Vis alle
Websøgning
Tilføj til startskærmen
@@ -40,6 +39,7 @@
Stream- eller købsindstillinger
Vælg region
Nulstil
+ Gem
Ryd søgehistorik
Søg episoder
@@ -149,7 +149,8 @@
Flyttet til skjulte
Serie er synlig igen
Tilføj serie
- Filtrér serier
+ Filter and sort
+ General
Fjern alle filtre
Foretrukne
Ikke set
@@ -160,7 +161,6 @@
Ekskluderet
Gør alle skjulte synlige
Gør alle skjulte serier (%s) synlige igen?
- Sortér serier
Titel
Seneste episode
Ældste episode
@@ -172,6 +172,8 @@
Næste episode set
Skift visning
Næste usete episode
+ Noter
+ Rediger note
Historik
Set for nylig
@@ -237,8 +239,10 @@
Lignende
Lignende serier
-
+
+ Opdag
Populær
+ Filter
År
Indeværende år
Sprog
@@ -256,12 +260,10 @@
Fjern fra liste
Fjernet fra listen
Du kan tilføje serier til denne liste
- Sortér lister
Omarrangér lister
Lister omarrangeret
En liste med dette navn findes allerede
- Opdag
Digitale udgivelser
Disk udgivelser
I biograferne
@@ -278,11 +280,10 @@
Trailer
Medvirkende
Besætning
- Sortér film
Titel
Udgivelsesdato
- Filtrer film
Lignende film
+ Udgivelsesdatoer
Kunne ikke beregne statistik.
%s set færdig
@@ -388,8 +389,6 @@
\"31. oktober\" i stedet for \"om 3 dage\"
Undgå spoilers
Skjul detaljer, indtil en episode er set
- Sortér sæsoner efter…
- Sortér episoder efter…
Foretrukket indholdssprog
Alternativt sprog
Download kun billeder via Wi-Fi
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 093bda4dd2..ba6c6f3173 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -28,7 +28,6 @@
Löschen
Verwerfen
- Auswahl speichern
Alle anzeigen
Web-Suche
Zum Startbildschirm hinzufügen
@@ -40,6 +39,7 @@
Streamen oder Kaufen Einstellungen
Region auswählen
Zurücksetzen
+ Speichern
Suchverlauf löschen
Suche Folgen
@@ -149,7 +149,8 @@
Serie ausgeblendet
Serie wieder sichtbar
Serie hinzufügen
- Serien filtern
+ Filtern und sortieren
+ Allgemein
Filter zurücksetzen
Favoriten
Nicht angesehen
@@ -160,7 +161,6 @@
Ausgeschlossen
Alle ausgebl. sichtbar machen
Alle ausgeblendeten Serien (%s) wieder sichtbar machen?
- Serien sortieren
Titel
Neueste Folge
Älteste Folge
@@ -172,6 +172,8 @@
Nächste Folge angesehen
Ansicht wechseln
Als nächstes ansehen
+ Notizen
+ Notiz bearbeiten
Verlauf
Vor kurzem angesehen
@@ -237,8 +239,10 @@
Ähnliche
Ähnliche Serien
-
+
+ Entdecken
Beliebt
+ Filtern
Jahr
Aktuelles Jahr
Sprache
@@ -247,21 +251,19 @@
Liste hinzufügen
Liste hinzugefügt
Liste benennen
- Liste entfernen
+ Liste löschen
Liste verwalten
Liste umbenannt
- Liste entfernt
+ Liste gelöscht
Listen verwalten
Liste aktualisiert
Von Liste entfernen
Von Liste entfernt
Sie können Serien zu dieser Liste hinzufügen
- Listen sortieren
Listen anordnen
Listen neu angeordnet
Eine Liste mit diesem Namen ist bereits vorhanden
- Entdecken
Neues in Digital
Neues auf Disc
Im Kino
@@ -278,11 +280,10 @@
Trailer
Besetzung
Crew
- Filme sortieren
Titel
Erscheinungsdatum
- Filme filtern
Ähnliche Filme
+ Veröffentlichungen
Statistik konnte nicht berechnet werden
%s komplett angesehen
@@ -388,8 +389,6 @@
\"31. Oktober\" anstelle von \"in 3 Tagen\"
Spoiler vermeiden
Details ausblenden, bis eine Folge angesehen wurde
- Sortiere Staffeln nach…
- Sortiere Folgen nach…
Bevorzugte Sprache der Inhalte
Alternative Sprache
Bilder nur über WLAN
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 189da0e2a9..1a0ce0582b 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -28,7 +28,6 @@
Καθαρισμός
Απόρριψη
- Αποθήκευση επιλογής
Εμφάνιση όλων
Αναζήτηση στον ιστό
Προσθήκη στην Αρχική οθόνη
@@ -40,6 +39,7 @@
Ρυθμίσεις streaming ή αγορών
Επιλογή περιοχής
Επαναφορά
+ Save
Εκκαθάριση ιστορικού αναζήτησης
Αναζήτηση επεισοδίων
@@ -149,7 +149,8 @@
Κρυφή σειρα
μη-κρυφή σειρά
Προσθήκη Σειράς
- Φίλτρο
+ Filter and sort
+ General
Κατάργηση όλων των φίλτρων
Αγαπημένα
Δεν έχουν παρακολουθηθεί
@@ -160,7 +161,6 @@
Εξαιρούνται
Εμφάνιση όλων των κρυφών
Να γίνουν όλες οι κρυφές σειρές (%s) φανερές ξανά;
- Ταξινόμηση σειρών
Τίτλος
Νεότερο επεισόδιο
Παλαιότερο επεισόδιο
@@ -172,6 +172,8 @@
Το επόμενο επεισόδιο έχει παρακολουθηθεί
Αλλαγή προβολής
Επόμενο για προβολή
+ Notes
+ Edit note
Ιστορικό
Παρακολουθήσατε πρόσφατα
@@ -237,8 +239,10 @@
Παρόμοια
Παρόμοιες σειρές
-
+
+ Ανακαλύψτε
Δημοφιλείς
+ Filter
Έτος
Τρέχον έτος
Γλώσσα
@@ -256,12 +260,10 @@
Κατάργηση από τη λίστα
Αφαιρέθηκε από τη λίστα
Μπορείτε να προσθέσετε σειρές σε αυτή τη λίστα
- Ταξινόμημένες λίστες
Ταξινομημένες λίστες
Οι λίστες αναδιατάχθηκαν
Υπάρχει ήδη μία λίστα με αυτό το όνομα
- Ανακαλύψτε
Ψηφιακές κυκλοφορίες
Κυκλοφορίες δίσκων
Στους κινηματογράφους
@@ -278,11 +280,10 @@
Τρέιλερ
Ηθοποιοί
Ομάδα ηθοποιών
- Ταξινόμηση ταινιών
Τίτλος
Ημερομηνία προβολής
- Φιλτράρισμα ταινιών
Παρόμοιες ταινίες
+ Ημερομηνίες κυκλοφορίας
Δε μπορέσαμε να υπολογίσουμε στατιστικά.
%s τελείωσε την προβολή
@@ -388,8 +389,6 @@
\"31 Οκτωβρίου\" αντί για \"σε 3 ημέρες\"
Να μην αποκαλύπτεται η υπόθεση των επεισοδίων
Απόκρυψη πληροφοριών μέχρι να παρακολουθήσετε το επεισόδιο
- Ταξινόμηση σεζόν κατά…
- Ταξινόμηση επεισοδίων κατά…
Προτιμώμενη γλώσσα περιεχομένου
Εναλλακτική γλώσσα
Εικόνες μόνο μέσω Wi-Fi
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
index e04745b477..5bde5769b1 100644
--- a/app/src/main/res/values-eo/strings.xml
+++ b/app/src/main/res/values-eo/strings.xml
@@ -28,7 +28,6 @@
Viŝi
Forĵeti
- Konservi elekton
Montri ĉiujn
Serĉi retume
Aldoni al Hejma ekrano
@@ -40,6 +39,7 @@
Stream or purchase settings
Elekti regionon
Reset
+ Save
Viŝi serĉhistorion
Serĉi epizodojn
@@ -149,7 +149,8 @@
Movita al kaŝitaj
Televidaĵo estas videbla denove
Aldoni televidaĵon
- Filtri televidaĵojn
+ Filter and sort
+ General
Forigi ĉiujn filtrilojn
Plej ŝatataj
Nespektitaj
@@ -160,7 +161,6 @@
Excluded
Videbligu ĉiujn kaŝitajn
Ĉu videbligu ĉiujn kaŝitajn televidaĵojn (%s) denove?
- Ordigi televidaĵojn
Titolo
Lasta epizodo
Plej maljuna epizodo
@@ -172,6 +172,8 @@
Spektis sekvan epizodon
Baskuli vidon
Next to watch
+ Notes
+ Edit note
Historio
Laste spektita
@@ -237,8 +239,10 @@
Similar
Similaj televidaĵoj
-
+
+ Esplori
Popularaj
+ Filter
Year
Current year
Language
@@ -250,18 +254,16 @@
Forigi liston
Administri liston
List renamed
- List removed
+ List deleted
Administri listojn
Lists updated
Forigi el listo
Removed from list
Vi povas aldoni televidaĵojn al ĉi tiu listo
- Ordigi listojn
Reordigi listojn
Lists reordered
Listo kun ĉi tiu nomo jam ekzistas
- Esplori
Bitaj eldonoj
Diskaj eldonoj
En kinejoj
@@ -278,11 +280,10 @@
Antaŭprezentaĵo
Aktoraro
Ekipo
- Ordigi filmojn
Titolo
Eldondato
- Filter movies
Similar movies
+ Release dates
Ne povas kalkuli statistikaĵojn
%s finis spektadi
@@ -388,8 +389,6 @@
\"Oktobro 31\" anstataŭ \"en 3 tagoj\"
Preventi malkaŝaĵojn
Hide details until an episode is watched
- Ordigi sezonojn laŭ…
- Ordigi epizodojn laŭ…
Preferata lingvo de enhavo
Alternativa lingvo
Bildojn nur per vifio
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 8ce2dcc37e..1250054aa6 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -28,7 +28,6 @@
Limpiar
Descartar
- Guardar selección
Ver todos
Búsqueda en la web
Agregar a la pantalla principal
@@ -40,6 +39,7 @@
Configuración de ver en línea o compra
Seleccionar región
Restablecer
+ Guardar
Limpiar historial de búsqueda
Buscar episodios
@@ -149,7 +149,8 @@
Se movió a ocultos
Mostrar serie
Añadir serie
- Filtrar series
+ Filtrar y ordenar
+ General
Quitar todos los filtros
Favoritos
No vistos
@@ -160,7 +161,6 @@
Excluido
Todo visible
¿Hacer visibles todas las series ocultas (%s) de nuevo?
- Ordenar series
Título
Último episodio
Episodio más antiguo
@@ -172,6 +172,8 @@
Siguiente episodio visto
Cambiar vista
Siguiente para ver
+ Notas
+ Editar nota
Historial
Vistas recientemente
@@ -237,8 +239,10 @@
Similares
Series parecidas
-
+
+ Descubrir
Populares
+ Filtrar
Año
Año actual
Idioma
@@ -256,12 +260,10 @@
Eliminar de la lista
Eliminado de la lista
Puedes añadir series a esta lista
- Ordenar listas
Reordenar listas
Listas reordenadas
Ya existe una lista con este nombre
- Descubrir
Estrenos digitales
Estrenos en disco
En cines
@@ -278,11 +280,10 @@
Trailer
Reparto
Equipo técnico
- Ordenar películas
Título
Fecha de estreno
- Filtrar películas
Películas similares
+ Fechas de lanzamiento
No se pudieron calcular las estadísticas.
%s vistos completamente
@@ -389,8 +390,6 @@ Asegúrate de quitarla también en tus otros dispositivos conectados.
\"El 31 de octubre\" en lugar de \"en 3 días\"
Evitar spoilers
Ocultar detalles hasta que un episodio sea visto
- Ordenar temporadas por…
- Ordenar episodios por…
Lenguaje preferido para el contenido
Idioma alternativo
Imágenes sólo por Wi-Fi
diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml
index 1cadc60fe8..b67bee692c 100644
--- a/app/src/main/res/values-fa/strings.xml
+++ b/app/src/main/res/values-fa/strings.xml
@@ -28,7 +28,6 @@
پاکسازی
نپذیرفتن
- ذخیرهٔ گزینش
نمایش همه
جستوجوی وب
افزودن به صفحه اصلی
@@ -40,6 +39,7 @@
Stream or purchase settings
گزینش ناحیه
بازنشانی
+ Save
پاکسازی تاریخچهٔ جستوجو
جستوجوی قسمتها
@@ -149,7 +149,8 @@
به پنهان منتقل شد
اثر نمایشی دوباره قابل مشاهده است
افزودن اثر نمایشی
- آثار نمایشی برحسب فیلتر
+ Filter and sort
+ General
حذف همه فیلترها
برگزیدهها
تماشا نشده
@@ -160,7 +161,6 @@
مستثتی شده
آشکارسازی همهی پنهانشدهها
آشکارسازی مجدد همهی (%s) برنامهی تلویزیونی?
- چینش آثار نمایشی
عنوان
جدیدترین قسمت
قدیمیترین قسمت
@@ -172,6 +172,8 @@
قسمت بعدی تماشا شد
تعویض نما
برنامهٔ بعدی
+ Notes
+ Edit note
تاریخچه
بهتازگی تماشا شده
@@ -237,8 +239,10 @@
مشابه
سریالهای مشابه
-
+
+ کشف
محبوب
+ Filter
سال
امسال
زبان
@@ -256,12 +260,10 @@
برداشتن از فهرست
از سیاهه برداشته شد
میتوانید نمایشها را به این فهرست بیفزایید
- چینش فهرستها
بازچینی فهرستها
ترتیب سیاهه عوض شد
فهرستی با این اسم موجود است
- کشف
نسخه های دیجیتال
منتشر شده روی دیسک
در سینماها
@@ -278,11 +280,10 @@
تریلر
بازیگران
عوامل پشت صحنه
- چینش فیلمها
عنوان
تاریخ انتشار
- پالایش فیلمها
فیلمهای مشابه
+ Release dates
نمیتوان آمار را محاسبه کرد.
%s تماشا پایان یافته
@@ -388,8 +389,6 @@
«۳۱ اکتبر» بهجای «در ۳ روز»
جلوگیری از لو رفتن
مخفی نگه داشتن جزئیات تا زمانی که قسمت تماشا شود
- چینش فصلها بر اساس…
- چینش قسمتها بر اساس…
زبان محتوای ترجیحی
Farsi
تصویرها فقط با وایفای
diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
index 83ce84566e..74ac8bb143 100644
--- a/app/src/main/res/values-fi/strings.xml
+++ b/app/src/main/res/values-fi/strings.xml
@@ -28,7 +28,6 @@
Tyhjennä
Peruuta
- Tallenna valinta
Näytä kaikki
Verkkohaku
Lisää aloitusnäytölle
@@ -40,6 +39,7 @@
Suoratoisto- tai ostoasetukset
Valitse alue
Nollaa
+ Tallenna
Tyhjennä hakuhistoria
Hae jaksoja
@@ -149,7 +149,8 @@
Siirretty piilotettuihin
Ohjelma on taas näkyvissä
Lisää ohjelma
- Suodata ohjelmat
+ Suodata ja lajittele
+ Yleiset
Poista kaikki suodattimet
Suosikit
Katsomattomat
@@ -160,7 +161,6 @@
Poissuljettu
Paljasta kaikki piilotetut
Paljastetaanko kaikki piilotetut ohjelmat (%s)?
- Lajittele ohjelmat
Nimi
Uusin jakso
Vanhin jakso
@@ -172,6 +172,8 @@
Seuraava jakso katsottu
Vaihda näkymä
Seuraava katsottava
+ Muistiinpanot
+ Muokkaa muistiinpanoa
Historia
Äskettäin katsotut
@@ -237,8 +239,10 @@
Samankaltaiset
Samankaltaisia sarjoja
-
+
+ Tutustu
Suositut
+ Suodata
Vuosi
Kuluva vuosi
Kieli
@@ -256,12 +260,10 @@
Poista listalta
Poistettiin listasta
Voit lisätä ohjelmia tähän listaan
- Lajittele listat
Järjestä listat uudelleen
Listat uudelleenjärjestettiin
Samanniminen lista on jo olemassa
- Tutustu
Digitaaliset julkaisut
Levyjulkaisut
Teattereissa
@@ -278,11 +280,10 @@
Traileri
Näyttelijät
Kuvausryhmä
- Lajittele elokuvat
Nimi
Julkaisupäivä
- Suodata elokuvat
Samankaltaisia elokuvia
+ Julkaisupäivät
Tilastojen laskeminen ei onnistunut.
%s katsottu loppuun
@@ -388,8 +389,6 @@
Käytä \"3 päivän kuluttua\" sijasta \"31. lokakuuta\"
Estä juonipaljastukset
Piilota tiedot, kunnes jakso on katsottu
- Lajittele kaudet…
- Lajittele jaksot…
Ensisijainen sisällön kieli
Vaihtoehtoinen kieli
Kuvat vain Wi-Fin kautta
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index b74196820b..7bc7dac5ce 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -28,7 +28,6 @@
Effacer
Annuler
- Enregistrer
Afficher tout
Recherche Web
Ajouter à l\'écran d\'accueil
@@ -36,10 +35,11 @@
Synchroniser manuellement
Synchroniser & Mettre à jour
Synchroniser & Télécharger tout
- Stream or purchase
- Options de streaming et d\'achat
+ Streaming ou achetez
+ Paramètres de streaming ou d\'achat
Sélectionner une région
Réinitialiser
+ Enregistrer
Effacer l\'historique de recherche
Recherche d\'épisodes
@@ -106,8 +106,8 @@
Source : %s
Dernière mise à jour
Modifier l\'heure de sortie
- Plus tôt
- Plus tard
+ plus tôt
+ plus tard
aujourd\'hui
maintenant
@@ -149,7 +149,8 @@
Série cachée
Série affichée
Ajouter une série
- Filtrer les séries
+ Filtrer et trier
+ Général
Enlever tous les filtres
Favoris
Non visionnés
@@ -160,7 +161,6 @@
Exclus
Rendre visible tous les cachés
Rendre les séries cachées (%s) visibles à nouveau ?
- Trier les séries
Titre
Dernier épisode
Épisode le plus ancien
@@ -172,6 +172,8 @@
Prochain épisode vu
Basculer l’affichage
Prochain à voir
+ Notes
+ Modifier la note
Historique
Vu récemment
@@ -212,12 +214,12 @@
- %1$d restants sur %2$d
- - %d skipped
- - %d skipped
+ - %d ignoré
+ - %d ignorés
- - %d in collection
- - %d in collection
+ - %d dans la collection
+ - %d dans la collection
Entrez un nom de série
@@ -237,10 +239,12 @@
Similaires
Séries similaires
-
+
+ A découvrir
Populaire
+ Filtrer
Année
- Cette année
+ Année en cours
Langue
Première liste
@@ -256,12 +260,10 @@
Retirer de la liste
Retiré de la liste
Vous pouvez ajouter des séries à cette liste.
- Trier les listes
Réorganiser les listes
Listes réordonnées
Une liste portant ce nom existe déjà
- A découvrir
Versions numériques
Sorties DVD
Dans les salles
@@ -278,11 +280,10 @@
Bande-annonce
Casting
Équipe technique
- Trier les films
Titre
Date de parution
- Filtrer les films
Films similaires
+ Dates de sortie
Impossible de calculer les statistiques.
%s visionnées en entier
@@ -388,8 +389,6 @@
« 31 octobre » au lieu de « dans 3 jours »
Éviter les spoilers
Masquer les détails jusqu\'à ce qu’un épisode soit vu
- Trier les saisons par…
- Trier les épisodes par…
Langue préférée pour le contenu
Langue alternative
Images uniquement via Wi-Fi
@@ -485,7 +484,7 @@
Notifications
M\'informer des nouveaux épisodes
Paramètres de notification
- Configurer l\'apparition des notifications
+ Configurez si et comment les notifications apparaissent
Sonnerie
Vibrer
Vibrer aussi lors d\'un notification
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 4b75de2998..04f0e1fb85 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -28,7 +28,6 @@
Limpar
Descartar
- Salvar seleción
Amosar todo
Pesquisa na web
Engadir á pantalla principal
@@ -40,6 +39,7 @@
Stream or purchase settings
Select region
Reset
+ Save
Limpar histórico de pesquisa
Procurar episodios
@@ -149,7 +149,8 @@
Serie ocultada
Serie novamente visíbel
Engadir serie
- Filtrar series
+ Filter and sort
+ General
Quitar tódolos filtros
Favoritos
Por ver
@@ -160,7 +161,6 @@
Excluded
Amosar todos os ocultos
Amosar todas as series ocultas (%s) de novo?
- Ordenar series
Título
Último episodio
Episodio máis antigo
@@ -172,6 +172,8 @@
Próximo episodio visto
Cambiar vista
Next to watch
+ Notes
+ Edit note
Histórico
Visto recentemente
@@ -237,8 +239,10 @@
Similar
Similar shows
-
+
+ Descubrir
Popular
+ Filter
Year
Current year
Language
@@ -250,18 +254,16 @@
Eliminar lista
Xestionar lista
List renamed
- List removed
+ List deleted
Xestionar listas
Lists updated
Eliminar da lista
Removed from list
You can add shows to this list
- Ordear listas
Reordear listas
Lists reordered
Xa existe unha lista con este nome
- Descubrir
Lanzamentos dixitais
Lanzamentos físicos
Nos cinemas
@@ -278,11 +280,10 @@
Trailer
Reparto
Equipo técnico
- Ordear películas
Título
Data de estrea
- Filter movies
Similar movies
+ Release dates
Non se puideron calcular as estatísticas
%s finished watching
@@ -388,8 +389,6 @@
\"31 de Outubro\" en vez de \"en 3 dias\"
Evitar spoilers
Ocultar detalles ata que un episodio sexa visto
- Ordenar temporadas por…
- Ordenar episodios por…
Idioma preferido para o contenido
Idioma alternativo
Imaxes só por Wi-Fi
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index 0f8cd8cf3a..21d1becfbc 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -28,7 +28,6 @@
Izbriši
Odbaci
- Pohrani odabrano
Prikaži sve
Pretraži web
Dodaj na početni zaslon
@@ -40,6 +39,7 @@
Stream or purchase settings
Select region
Reset
+ Save
Očisti povijest pretraživanja
Pretraži epizode
@@ -150,7 +150,8 @@
Sakrivena serija
Prikazana serija
Dodaj seriju
- Filtriraj serije
+ Filter and sort
+ General
Ukloni sve filtere
Favoriti
Nije pogledano
@@ -161,7 +162,6 @@
Excluded
Postavi sve skrivene kao vidljive
Prikaži ponovno sve (%s) skrivene serije?
- Sortiraj serije
Naslov
Zadnja epizoda
Najstarija epizoda
@@ -173,6 +173,8 @@
Pogledana sljedeća epizoda
Promijeni pogled
Next to watch
+ Notes
+ Edit note
Povijest
Nedavno pogledano
@@ -244,8 +246,10 @@
Similar
Slične serije
-
+
+ Otkrij
Popularno
+ Filter
Year
Current year
Language
@@ -257,18 +261,16 @@
Ukloni popis
Uredi popis
List renamed
- List removed
+ List deleted
Uredi popise
Lists updated
Ukloni s popisa
Removed from list
You can add shows to this list
- Sortiraj popise
Promijeni redoslijed popisa
Lists reordered
Popis sa ovim nazivom već postoji
- Otkrij
Digitalna izdanja
Disk izdanja
U kinima
@@ -285,11 +287,10 @@
Trailer
Uloge
Ekipa
- Kratki filmovi
Naslov
Datum emitiranja
- Filter movies
Similar movies
+ Release dates
Nije moguće izračunati statistike.
%s finished watching
@@ -399,8 +400,6 @@
\"31 listopad\" umjesto \"za 3 dana\"
Spriječi spojlere
Sakrij detalje dok epizoda nije pogledana
- Poredaj sezone prema…
- Poredaj epizode prema…
Preferirani jezik sadržaja
Alternativni jezik
Slike samo putem Wi-Fi mreže
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 5353cbd8fb..b86219b77b 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -28,7 +28,6 @@
Törlés
Elvetés
- Kijelöltek mentése
Összes megjelenítése
Internetes keresés
Hozzáadás a kezdőképernyőhöz
@@ -40,6 +39,7 @@
Stream or purchase settings
Válassz régiót
Visszaállítás
+ Save
Keresési előzmények törlése
Epizódok keresése
@@ -149,7 +149,8 @@
Rejtett sorozat
Látható műsor
Sorozat hozzáadása
- Sorozatok szűrése
+ Filter and sort
+ General
Minden szűrő eltávolítása
Kedvencek
Nem látott
@@ -160,7 +161,6 @@
Kizárva
Elrejtett szöveg mutatása
Az összes elrejtett sorozat (%s) mutatása?
- Sorozatok rendezése
Cím
Legújabb epizód
Legrégebbi epizód
@@ -172,6 +172,8 @@
Láttam a következő epizódot
Nézet váltása
Következő megnézendő
+ Notes
+ Edit note
Előzmények
Nemrég megnézett
@@ -237,8 +239,10 @@
Hasonló
Hasonló sorozatok
-
+
+ Felfedezés
Népszerű
+ Filter
Year
Current year
Language
@@ -250,18 +254,16 @@
Lista törlése
Lista kezelése
List renamed
- List removed
+ List deleted
Listák kezelése
Lists updated
Eltávolítás a listáról
Removed from list
Sorozatokat adhat hozzá a listhoz
- Listák rendezése
Listák újrarendezése
Lists reordered
Már létezik egy lista ezzel a névvel
- Felfedezés
Digitalis megjelenések
Lemez megjelenések
Mozikban
@@ -278,11 +280,10 @@
Előzetes
Szereplők
Stáb
- Filmek rendezése
Cím
Első adás
- Filmek szűrése
Hasonló filmek
+ Release dates
Nem sikerült kiszámítani a statisztikát.
%s teljesen megnézve
@@ -388,8 +389,6 @@
\"Október 31\" a \"3 napon belül\" helyett
Spoilerek megakadályozása
Részletek elrejtése, amíg az epizód nincs megnézve
- Évadok rendezése
- Epizódok rendezése
Tartalom preferált nyelve
Alternatív nyelv
Képek csak Wi-Fi-n keresztül
diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml
index 18d3d08b6c..26e4d0035f 100644
--- a/app/src/main/res/values-in/strings.xml
+++ b/app/src/main/res/values-in/strings.xml
@@ -28,7 +28,6 @@
Bersihkan
Singkirkan
- Simpan bagian
Tunjukan semua
Pencarian Web
Tambahkan ke beranda
@@ -40,6 +39,7 @@
Stream or purchase settings
Pilih wilayah
Reset
+ Save
Bersihkan riwayat pencarian
Cari episode
@@ -148,7 +148,8 @@
Dipindahkan ke tersembunyi
Acara terlihat kembali
Tambahkan acara
- Saring acara
+ Filter and sort
+ General
Hapus semua filter
Favorit
Belum ditonton
@@ -159,7 +160,6 @@
Excluded
Perlihatkan yang tersembunyi
Buat semua acara tersembunyi (%s) terlihat lagi ?
- Sortir acara
Judul
Episode Terbaru
Episode Terlama
@@ -171,6 +171,8 @@
Episode selanjutnya ditonton
Ganti tampilan
Next to watch
+ Notes
+ Edit note
Riwayat
Baru-baru ini ditonton
@@ -230,8 +232,10 @@
Similar
Acara Serupa
-
+
+ Jelajahi
Populer
+ Filter
Year
Current year
Language
@@ -243,18 +247,16 @@
Hapus daftar
Kelola daftar
List renamed
- List removed
+ List deleted
Kelola daftar
Lists updated
Hapus dari daftar
Removed from list
Kau bisa menambahkan acara ke daftar ini
- Urutkan daftar
Susun ulang daftar
Lists reordered
Daftar dengan nama ini sudah ada
- Jelajahi
Rilisan digital
Rilisan cakram
Di bioskop
@@ -271,11 +273,10 @@
Cuplikan
Pemeran
Kru
- Sortir film
Judul
Tanggal rilis
- Filter movies
Similar movies
+ Release dates
Tidak dapat menghitung statistik.
%s selesai ditonton
@@ -377,8 +378,6 @@
\"31 Oktober\" daripada \"dalam 3 hari\"
Cegah bocoran
Sembunyikan rician sampai satu episode ditonton
- Sortir musim berdasarkan…
- Sortir musim berdasarkan…
Bahasa konten yang diutamakan
Alternatif bahasa
Gambar hanya melalui Wi-fi
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 7cb4cb3352..33c043ea79 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -28,7 +28,6 @@
Cancella
Scarta
- Salva selezione
Mostra tutti
Ricerca Web
Aggiungi alla schermata Home
@@ -40,6 +39,7 @@
Streaming o impostazioni acquisto
Seleziona regione
Reimposta
+ Salva
Cancella la cronologia di ricerca
Cerca episodi
@@ -149,7 +149,8 @@
Spostato nei non visibili
Visualizza non nascosti
Aggiungi una serie
- Filtra le serie
+ Filtra e ordina
+ Generale
Rimuovi tutti i filtri
Preferiti
Non visti
@@ -160,7 +161,6 @@
Esclusi
Rendi visibili tutti i nascosti
Rendere tutte le serie TV nascoste (%s) di nuovo visibili?
- Ordina le serie
Titolo
Episodio più recente
Episodio più vecchio
@@ -172,6 +172,8 @@
Episodio seguente visto
Cambia vista
Prossimo da vedere
+ Note
+ Modifica nota
Cronologia
Visti di recente
@@ -237,8 +239,10 @@
Simili
Serie simili
-
+
+ Scopri
Popolari
+ Filtra
Anno
Quest\'anno
Lingua
@@ -256,12 +260,10 @@
Rimuovi dalla lista
Rimosso dalla lista
Puoi aggiungere delle serie a questa lista
- Ordina liste
Riordina le liste
Lista riordinata
Una lista con questo nome esiste già
- Scopri
Uscite in digitale
Uscite su disco
Al cinema
@@ -278,11 +280,10 @@
Trailer
Cast
Squadra
- Ordina film
Titolo
Data di uscita
- Filtra i film
Film simili
+ Date di uscita
Impossibile calcolare le statistiche
%s visti completamente
@@ -388,8 +389,6 @@
\"31 Ottobre\" invece di \"tra 3 giorni\"
Evita spoiler
Nascondi i dettagli fino a quando l\'episodio è visto
- Ordina stagioni per…
- Ordina episodi per…
Lingua preferita per i contenuti
Lingua alternativa
Immagini solo in Wi-Fi
diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
index b3252b0aaf..3957c2d419 100644
--- a/app/src/main/res/values-iw/strings.xml
+++ b/app/src/main/res/values-iw/strings.xml
@@ -28,7 +28,6 @@
נקה
בטל
- שמור בחירה
הצג הכל
חיפוש באינטרנט
הוסף לדף הבית
@@ -40,6 +39,7 @@
Stream or purchase settings
Select region
Reset
+ Save
נקה את היסטוריית החיפוש
חפש פרקים
@@ -151,7 +151,8 @@
סדרה מוסתרת
סדרה לא מוסתרת
הוספת סדרה
- סינון סדרות
+ Filter and sort
+ General
הסרת כל המסננים
מועדפים
טרם נצפה
@@ -162,7 +163,6 @@
לא כולל
הצג את כל המוסתרים
הצג שוב את כל הסדרות המוסתרות (%s)?
- מיון סדרות
שם
הפרק האחרון
הפרק הכי ישן
@@ -174,6 +174,8 @@
צפה בפרק הבא
שינוי מצב
Next to watch
+ Notes
+ Edit note
היסטוריה
נצפה לאחרונה
@@ -251,8 +253,10 @@
Similar
סדרות דומות
-
+
+ גלו עוד
פופולרי
+ Filter
Year
Current year
Language
@@ -264,18 +268,16 @@
הסר רשימה
נהל רשימה
List renamed
- List removed
+ List deleted
נהל רשימות
Lists updated
הסר מהרשימה
Removed from list
You can add shows to this list
- מיין רשימה
סדר מחדש רשימות
Lists reordered
רשימה עם השם הזה כבר קיימת
- גלו עוד
שיחרור דיגיטלי
שיחרור מדיסק
בבתי הקולנוע
@@ -292,11 +294,10 @@
טריילר
משתתפים
צוות
- מיין סרטים
שם
תאריך הפצה
- Filter movies
Similar movies
+ Release dates
לא ניתן לחשב את הסטטיסטיקה.
%s finished watching
@@ -410,8 +411,6 @@
\"31 באוקטובר\" במקום \"ב-3 ימים\"
מנע ספוילרים
הסתר פרטים עד שנצפה בפרק
- מיין עונות לפי
- מיין פרקים לפי
שפת תוכן מועדפת
שפה חלופית
תמונות באמצעות Wi-Fi בלבד
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index a289abbb17..8f0071ea5c 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -28,7 +28,6 @@
削除
破棄
- 選択した内容を保存
すべて表示
ウェブ検索
ホーム画面に追加
@@ -40,6 +39,7 @@
Stream or purchase settings
エリアの選択
Reset
+ Save
検索履歴を削除
エピソードを検索
@@ -148,7 +148,8 @@
非表示に移動しました
番組は再び表示されます
番組を追加
- 番組をフィルター
+ Filter and sort
+ General
すべてのフィルターを削除
お気に入り
未視聴
@@ -159,7 +160,6 @@
Excluded
非表示をすべて表示する
非表示の番組 (%s) をもう一度すべて表示しますか?
- 番組を並び替え
タイトル
最新のエピソード
最も古いエピソード
@@ -171,6 +171,8 @@
視聴済次のエピソード
表示切替
Next to watch
+ Notes
+ Edit note
履歴
最近観た作品
@@ -230,8 +232,10 @@
Similar
類似の番組
-
+
+ 見つける
人気
+ Filter
Year
Current year
Language
@@ -243,18 +247,16 @@
リストを削除
リストを管理
List renamed
- List removed
+ List deleted
リストを管理
Lists updated
リストから削除
Removed from list
You can add shows to this list
- リストの並べ替え
リストの再並べ替え
Lists reordered
この名前のリストがすでに存在します
- 見つける
デジタル リリース
ディスク リリース
上映中
@@ -271,11 +273,10 @@
予告編
キャスト
スタッフ
- 映画の並べ替え
タイトル
発売日
- Filter movies
Similar movies
+ Release dates
統計情報を計算できませんでした。
%s finished watching
@@ -377,8 +378,6 @@
「3 日以内」ではなく「10 月 31 日」
ネタバレを防ぐ
エピソードを見るまで詳細を非表示
- シーズンを並び替え…
- エピソードを並び替え…
ご希望のコンテンツ言語
別の言語
画像は Wi-Fi 経由のみ
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 963bf34fba..42ccf96756 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -28,7 +28,6 @@
지우기
취소
- 선택 항목 저장 완료
모두 보기
웹 검색
홈 화면에 추가
@@ -40,6 +39,7 @@
Stream or purchase settings
국가 선택
재설정
+ Save
검색 기록 삭제
에피소드 검색
@@ -148,7 +148,8 @@
숨김 완료
표시 완료
프로그램 추가
- 프로그램 필터
+ Filter and sort
+ General
모든 필터 제거
즐겨찾기
미시청
@@ -159,7 +160,6 @@
Excluded
Make all hidden visible
Make all hidden shows (%s) visible again?
- 프로그램 정렬
제목
최근 에피소드
오래된 에피소드
@@ -171,6 +171,8 @@
다음 에피소드 시청 완료
Switch view
Next to watch
+ Notes
+ Edit note
기록
최근 시청 항목
@@ -230,8 +232,10 @@
Similar
Similar shows
-
+
+ 찾기
인기 항목
+ Filter
Year
Current year
Language
@@ -243,18 +247,16 @@
목록 제거
목록 관리
List renamed
- List removed
+ List deleted
목록 관리
Lists updated
목록에서 제거
Removed from list
You can add shows to this list
- 목록 정렬
목록 재정렬
Lists reordered
이 이름을 가진 목록이 이미 존재합니다
- 찾기
디지털 출시
디스크 출시
상영 중
@@ -271,11 +273,10 @@
예고편
출연진
제작진
- 영화 정렬
제목
개봉일
- Filter movies
Similar movies
+ Release dates
통계를 계산할 수 없습니다.
%s finished watching
@@ -377,8 +378,6 @@
\"10월 31일\" 대신 \"3일안에\"
스포일러 방지
시청할 때까지 세부 정보를 숨깁니다.
- 시즌 정렬
- 에피소드 정렬
선호하는 컨텐츠 언어
기타 언어
와이파이를 이용하여 이미지 다운받기
diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml
index 258cb61975..47d6e18812 100644
--- a/app/src/main/res/values-mk/strings.xml
+++ b/app/src/main/res/values-mk/strings.xml
@@ -28,7 +28,6 @@
Избриши
Откажи
- Зачувај селектирано
Прикажи се
Веб пребарување
Додај на Почетен екран
@@ -40,6 +39,7 @@
Stream or purchase settings
Select region
Reset
+ Save
Избриши историја на пребарување
Пребарај епизоди
@@ -149,7 +149,8 @@
Премести во скриени
Серијата е видлива повторно
Додади серија
- Филтрирај серија
+ Filter and sort
+ General
Избриши ги сите филтри
Омилени
Не гледани
@@ -160,7 +161,6 @@
Excluded
Make all hidden visible
Make all hidden shows (%s) visible again?
- Подреди серии
Наслов
Следна епизода
Најстара епизода
@@ -172,6 +172,8 @@
Гледај следна епизода
Switch view
Next to watch
+ Notes
+ Edit note
Историја
Скоро гледана
@@ -237,8 +239,10 @@
Similar
Similar shows
-
+
+ Откриј
Популарни
+ Filter
Year
Current year
Language
@@ -250,18 +254,16 @@
Избриши листа
Уреди листа
List renamed
- List removed
+ List deleted
Уреду листи
Lists updated
Избриши од листа
Removed from list
You can add shows to this list
- Листа за сортирање
Поново сортирајте листа
Lists reordered
Една група со ова име веќе постои
- Откриј
Дигитално издание
Диск изданија
Во кината
@@ -278,11 +280,10 @@
Трејлер
Улоги
Екипа
- Сортирај филмови
Наслов
Датум на емитување
- Filter movies
Similar movies
+ Release dates
Неможе да се пресмета статистика.
%s finished watching
@@ -388,8 +389,6 @@
\"Октомври 31\" отколку \"во 3 денови\"
Спречи спојлери
Сокриј детали додека епизодата не е гледана
- Сортирај сезоните по…
- Сортирај по епизоди…
Предпрoчитaн јaзик нa сoдржинaтa
Алтернативен јазик
Слики од Wi-Fi само
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index cdebdf6bad..538d816cd4 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -28,7 +28,6 @@
Fjern
Avbryt
- Lagre valg
Vis alle
Nettsøk
Legg til på hjem-skjermen
@@ -40,6 +39,7 @@
Stream or purchase settings
Velg region
Reset
+ Save
Tøm søkelogg
Søk episoder
@@ -149,7 +149,8 @@
Skjuler serie
Viser serie igjen
Legg til serie
- Filtrer serier
+ Filter and sort
+ General
Fjern alle filtre
Favoritter
Ikke sett
@@ -160,7 +161,6 @@
Excluded
Vis alle skjulte
Gjøre alle skjulte programmer (%s) synlige igjen?
- Sorter serier
Tittel
Nyeste episode
Eldste episode
@@ -172,6 +172,8 @@
Sett neste episode
Bytt visning
Next to watch
+ Notes
+ Edit note
Historikk
Nylig sett
@@ -237,8 +239,10 @@
Similar
Lignende serier
-
+
+ Oppdag
Populær
+ Filter
Year
Current year
Language
@@ -250,18 +254,16 @@
Fjern liste
Behandle liste
List renamed
- List removed
+ List deleted
Behandle lister
Lists updated
Fjern fra liste
Removed from list
Du kan legge til serier i denne listen
- Sorter lister
Omorganiser lister
Lists reordered
En liste med dette navnet finnes allerede
- Oppdag
Digitale utgaver
Utgaver på disc
På kino
@@ -278,11 +280,10 @@
Trailer
Medvirkende
Mannskap
- Sorter filmer
Tittel
Utgivelsesdato
- Filter movies
Similar movies
+ Release dates
Kunne ikke beregne statistikk.
%s finished watching
@@ -388,8 +389,6 @@
\"31. Oktober\" i stedet for \"om 3 dager\"
Unngå spoilere
Skjul detaljer til episoden er sett
- Sorter sesonger etter…
- Sorter episoder etter…
Foretrukket språk
Alternativt språk
Bilder kun via Wi-Fi
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index fd8c8d373b..630ad10448 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -28,7 +28,6 @@
Wissen
Negeren
- Selectie bewaren
Toon alles
Zoeken op het web
Toevoegen aan startscherm
@@ -40,6 +39,7 @@
Streamen- of aankoopinstellingen
Regio selecteren
Herstellen
+ Opslaan
Verwijder zoekgeschiedenis
Afleveringen zoeken
@@ -149,7 +149,8 @@
Verborgen serie
Serie is opnieuw zichtbaar
Serie toevoegen
- Series filteren
+ Filteren en sorteren
+ Algemeen
Alle filters verwijderen
Favorieten
Onbekeken
@@ -160,7 +161,6 @@
Uitgesloten
Alle verborgen tonen
Alle verborgen series (%s) opnieuw zichtbaar maken?
- Series sorteren
Titel
Laatste aflevering
Oudste aflevering
@@ -172,6 +172,8 @@
Volgende aflevering bekeken
Wissel weergave
Volgende om te bekijken
+ Notities
+ Notitie bewerken
Geschiedenis
Laatst gekeken
@@ -237,8 +239,10 @@
Soortgelijk
Soortgelijke shows
-
+
+ Ontdekken
Populair
+ Filteren
Jaar
Huidig jaar
Taal
@@ -256,12 +260,10 @@
Verwijderen uit lijst
Verwijderd uit lijst
U kunt series toevoegen aan deze lijst
- Lijst sorteren
Lijsten opnieuw ordenen
Lijsten gerangschikt
Een lijst met deze naam bestaat al
- Ontdekken
Digitale releases
Releases op schijf
In de bioscoop
@@ -278,11 +280,10 @@
Trailer
Cast
Filmploeg
- Sorteer films
Titel
Releasedatum
- Films filteren
Vergelijkbare films
+ Releasedatums
Kon statistieken niet berekenen.
%s klaar met kijken
@@ -388,8 +389,6 @@
\"31 oktober\" in plaats van \"over 3 dagen\"
Spoilers voorkomen
Details verbergen totdat een aflevering is bekeken
- Seizoenen sorteren op…
- Afleveringen sorteren op…
Voorkeurstaal voor inhoud
Alternatieve taal
Afbeeldingen alleen via Wi-Fi
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index e8121e4255..5b3a5c159f 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -28,7 +28,6 @@
Wyczyść
Anuluj
- Zapisz zaznaczone
Pokaż wszystko
Wyszukiwarka
Dodaj do strony głównej
@@ -40,6 +39,7 @@
Opcje kupowania i strumieniowania
Wybierz region
Przywróć
+ Zapisz
Wyczyść historię wyszukiwania
Szukaj odcinków
@@ -151,7 +151,8 @@
Serial został ukryty
Odkryty serial
Dodaj serial
- Filtruj seriale
+ Filtruj i sortuj
+ Ogólne
Usuń wszystkie filtry
Ulubione
Nieobejrzane
@@ -162,7 +163,6 @@
Wykluczone
Przywróć ukryte seriale
Przywrócić wszystkie ukryte seriale (%s)?
- Sortuj seriale
Tytuł
Najnowszy odcinek
Najstarszy odcinek
@@ -174,6 +174,8 @@
Odcinek obejrzany
Przełącz widok
Następny do obejrzenia
+ Notatki
+ Edytuj notatkę
Historia
Niedawno obejrzane
@@ -251,8 +253,10 @@
Podobne
Podobne seriale
-
+
+ Odkrywaj
Popularne
+ Filtruj
Rok
Bieżący rok
Język
@@ -270,12 +274,10 @@
Usuń z listy
Usunięto z listy
Możesz dodać seriale do tej listy
- Sortowanie list
Zmień kolejność list
Lista przeorganizowana
Lista o tej nazwie już istnieje
- Odkrywaj
Wydania cyfrowe
Na płytach
W kinach
@@ -292,11 +294,10 @@
Zwiastun
Obsada
Ekipa filmowa
- Sortuj filmy
Tytuł
Data premiery
- Filtruj filmy
Podobne filmy
+ Daty premier
Nie można obliczyć statystyk.
Zakończono oglądanie %s
@@ -410,8 +411,6 @@
\"31 października\" zamiast \"za 3 dni\"
Ukryj spoilery
Ukrywa szczegóły do czasu obejrzenia odcinka
- Sortuj sezony wg…
- Sortuj odcinki wg…
Preferowany język zawartości
Język alternatywny
Obrazy tylko przez Wi-Fi
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 17e2e854e2..0a793773e9 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -28,7 +28,6 @@
Limpar
Descartar
- Salvar seleção
Mostrar tudo
Pesquisa na Web
Adicionar à tela inicial
@@ -40,6 +39,7 @@
Configurações de streaming ou compra
Selecionar região
Redefinir
+ Save
Limpar histórico de pesquisa
Pesquisar episódios
@@ -149,7 +149,8 @@
Ocultar série
Série visível novamente
Adicionar série
- Filtrar séries
+ Filter and sort
+ General
Remover todos os filtros
Favoritos
Não assistidos
@@ -160,7 +161,6 @@
Excluído
Exibir todos os ocultos
Tornar todas as séries ocultas (%s) visíveis novamente?
- Ordenar séries
Título
Último episódio
Episódio mais antigo
@@ -172,6 +172,8 @@
Próximo episódio já assistido
Alterar visualização
Próximo a assistir
+ Notes
+ Edit note
Histórico
Recentemente assistido
@@ -237,8 +239,10 @@
Similares
Séries similares
-
+
+ Descubra
Popular
+ Filter
Ano
Ano atual
Idioma
@@ -256,12 +260,10 @@
Remover da lista
Removido da lista
Você pode adicionar séries à lista
- Ordenar listas
Reordenar listas
Listas reordenadas
Já existe uma lista com este nome
- Descubra
Lançamentos digitais
Lançamentos em disco
Nos cinemas
@@ -278,11 +280,10 @@
Trailer
Elenco
Equipe
- Ordenar filmes
Título
Data de lançamento
- Filtrar filmes
Filmes parecidos
+ Datas de lançamento
Não é possível calcular as estatísticas.
%s assistido completamente
@@ -388,8 +389,6 @@
\"31 de Outubro\" em vez de \"em 3 dias\"
Evitar spoilers
Oculte detalhes até que o episódio seja assistido
- Ordenar temporadas por…
- Ordenar episódios por…
Idioma preferido do conteúdo
Idioma alternativo
Imagens apenas via Wi-Fi
diff --git a/app/src/main/res/values-pt-rPT/strings.xml b/app/src/main/res/values-pt-rPT/strings.xml
index 2d11b90f9c..0dfcfaee89 100644
--- a/app/src/main/res/values-pt-rPT/strings.xml
+++ b/app/src/main/res/values-pt-rPT/strings.xml
@@ -28,18 +28,18 @@
Limpar
Cancelar
- Guardar opções selecionadas
Mostrar tudo
Pesquisar na Internet
Adicionar ao Ecrã inicial
Abrir
Sincronizar manualmente
- Sincronizar e Atualizar
- Sincronizar e Retransferir
+ Sincronizar e atualizar
+ Sincronizar e retransferir
Comprar ou ver em streaming
Definições de streaming ou compra
Selecionar região
Reiniciar
+ Guardar
Limpar histórico de pesquisa
Procurar episódios
@@ -149,7 +149,8 @@
Série ocultada
Série novamente visível
Adicionar série
- Filtrar séries
+ Filtrar e ordenar
+ Geral
Remover todos os filtros
Favoritos
Por ver
@@ -160,7 +161,6 @@
Excluído
Tornar os ocultos visíveis
Tornar todas as séries ocultas (%s) visíveis novamente?
- Ordenar séries
Título
Episódios mais recentes
Episódios mais antigos
@@ -172,6 +172,8 @@
Marcar próximo episódio como visto
Alterar visualização
Próximo a ser visto
+ Notas
+ Editar nota
Histórico
Visto recentemente
@@ -237,8 +239,10 @@
Semelhantes
Séries semelhantes
-
+
+ Explorar
Populares
+ Filtro
Ano
Ano atual
Idioma
@@ -256,12 +260,10 @@
Remover da lista
Removido da lista
Podes adicionar séries à esta lista
- Ordenar listas
Reordenar listas
Listas reordenadas
Já existe uma lista com este nome
- Explorar
Lançamentos Digitais
Lançamentos Físicos
Nos Cinemas
@@ -277,12 +279,11 @@
Não adicionaste filmes à lista dos que desejas ver.
Trailer
Elenco
- Equipa Técnica
- Ordenar filmes
+ Equipa técnica
Título
Data de estreia
- Filtrar filmes
Filmes semelhantes
+ Datas de estreia
Não foi possível calcular as estatísticas.
%s vistas completamente
@@ -378,7 +379,7 @@
Enviar e-mail
Traduzir esta aplicação
- Avançado
+ Avançadas
Sobre
Nenhum episódio em exibição
Não mostrar episódios que já tenham estreado
@@ -388,8 +389,6 @@
\"31 de Outubro\" em vez de \"Dentro de 3 dias\"
Prevenir spoilers
Ocultar detalhes até o episódio ter sido visto
- Ordenar temporadas por…
- Ordenar episódios por…
Idioma preferido para o conteúdo
Idioma alternativo
Atualizar imagens apenas por Wi-Fi
@@ -456,7 +455,7 @@
Incluir as descrições, os detalhes e as classificações
Cópia de segurança interrompida. Uma cópia de segurança não pôde ser encontrada ou acedida.
Cópia de segurança bem sucedida.
- A cópia de segurança não foi efectuada com sucesso.
+ A cópia de segurança não foi efetuada com sucesso.
Não é possível restaurar a cópia de segurança. A pasta das cópias de segurança não se encontra disponível.
Não existe nenhuma cópia de segurança que possa ser lida.
Cópia de segurança restaurada com sucesso.
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index d8ba078ca0..44caae8e92 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -28,7 +28,6 @@
Ștergere
Renunță
- Salvează selecția
Arată tot
Căutare pe web
Adaugă la ecranul de principal
@@ -40,6 +39,7 @@
Stream or purchase settings
Select region
Reset
+ Save
Ștergere istoric căutare
Căutaţi episoade
@@ -150,7 +150,8 @@
Emisiune ascunsă
Emisiune neascunsă
Adaugă serial
- Filtrează serialele
+ Filter and sort
+ General
Ștergeți toate filtrele
Favorite
Nevizualizate
@@ -161,7 +162,6 @@
Excluded
Make all hidden visible
Make all hidden shows (%s) visible again?
- Sortați serialele
Titlu
Cel mai recent episod
Cel mai vechi episod
@@ -173,6 +173,8 @@
Episodul următor a fost vizionat
Switch view
Next to watch
+ Notes
+ Edit note
Istoric
Recent vizionate
@@ -244,8 +246,10 @@
Similar
Similar shows
-
+
+ Descoperă
Popular
+ Filter
Year
Current year
Language
@@ -257,18 +261,16 @@
Şterge listă
Gestionați lista
List renamed
- List removed
+ List deleted
Gestionați listele
Lists updated
Șterge din listă
Removed from list
You can add shows to this list
- Sortează lista
Reordonaţi lista
Lists reordered
O listă cu acest nume deja există
- Descoperă
Lansări digitale
Lansări pe disc
În cinematografe
@@ -285,11 +287,10 @@
Trailer
Distributie
Echipa
- Sortare filme
Titlu
Data de lansare
- Filter movies
Similar movies
+ Release dates
Nu am putut calcula statisticile.
%s finished watching
@@ -399,8 +400,6 @@
\"31 Octombrie\" în loc de \"în 3 zile\"
Ascunde spoilerele
Ascunde detaliile până episodul este vizionat
- Sortare sezoane după…
- Sortare episoade după…
Limba preferată
Limba alternativa
Imaginile se vor încarca doar prin intermediul Wi-Fi
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 8872088544..9f0a045442 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -28,7 +28,6 @@
Удалить
Отменить
- Сохранить выбор
Показать все
Веб-поиск
Добавить на главный экран
@@ -40,6 +39,7 @@
Настройка трансляции или покупка
Выберите регион
Сброс
+ Сохранить
Очистить историю поиска
Поиск эпизодов
@@ -151,7 +151,8 @@
Перемещено в скрытые
Сериал снова виден
Добавить сериал
- Фильтр сериалов
+ Фильтр и сортировка
+ Общее
Очистить фильтры
Избранное
Непросмотренные
@@ -162,7 +163,6 @@
Исключено
Сделать всё скрытое видимым
Сделать все скрытые сериалы (%s) снова видимыми?
- Сортировать сериалы
Название
Последний эпизод
Старейший эпизод
@@ -174,6 +174,8 @@
Смотреть следующий эпизод
Изменить вид
Далее к просмотру
+ Заметка
+ Редактировать заметку
История
Недавно просмотренные
@@ -251,8 +253,10 @@
Похожие
Похожие сериалы
-
+
+ Подборка
Популярные
+ Фильтр
Год
Текущий год
Язык
@@ -270,12 +274,10 @@
Удалить из списка
Удалено из списка
Вы можете добавить сериалы в этот список
- Отсортировать списки
Сортировка списков
Списки переупорядочены
Список с таким именем уже существует
- Подборка
Цифровые версии
Диски
Смотрите в кинотеатрах
@@ -292,11 +294,10 @@
Трейлер
Актерский состав
Съёмочная группа
- Сортировать фильмы
Название
Дата выпуска
- Фильтр фильмов
Похожие фильмы
+ Даты выпуска
Не удалось рассчитать статистику.
%s завершенных просмотров
@@ -410,8 +411,6 @@
\"31 октября\" вместо \"Через 3 дня\"
Предотвратить спойлеры
Скрывать подробности, пока эпизод не просмотрен
- Сортировка сезонов по…
- Сортировка эпизодов по…
Предпочитаемый язык контента
Альтернативное изображение
Картинки только через Wi-Fi
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index 1ddaadc096..10540186bd 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -28,7 +28,6 @@
Zmazať
Zrušiť
- Uložiť výber
Zobraziť všetko
Hľadať na webe
Pridať na domovskú obrazovku
@@ -40,6 +39,7 @@
Stream or purchase settings
Vybrať región
Resetovať
+ Uložiť
Vymazať históriu vyhľadávania
Hľadať epizódy
@@ -151,7 +151,8 @@
Skrytý seriál
Obnovený seriál
Pridať seriál
- Filtrovať seriály
+ Filter and sort
+ General
Odstrániť všetky filtre
Obľúbené
Nevidené
@@ -162,7 +163,6 @@
Vylúčené
Zviditeľniť všetky skryté
Zviditeľniť všetky skryté seriály (%s)?
- Zoradiť seriály
Názov
Najnovšia epizóda
Najstaršia epizóda
@@ -174,6 +174,8 @@
Označiť nasledujúcu ako videnú
Zmeniť zobrazenie
Ďalšie na sledovanie
+ Poznámky
+ Upraviť poznámku
História
Nedávno videné
@@ -251,8 +253,10 @@
Podobné
Podobné relácie
-
+
+ Objaviť
Populárne
+ Filter
Rok
Aktuálny rok
Jazyk
@@ -270,12 +274,10 @@
Odstrániť zo zoznamu
Odstránené zo zoznamu
Môžete pridať seriály do tohto zoznamu
- Zoradiť zoznamy
Zoradiť zoznamy
Poradie zoznamov zmenené
Zoznam s týmto názvom už existuje
- Objaviť
Digitálne médiá
Diskové médiá
V kinách
@@ -292,11 +294,10 @@
Ukážka
Obsadenie
Kolektív
- Zoradiť filmy
Názov
Dátum premiéry
- Filtrovať filmy
Podobné filmy
+ Release dates
Nemožno vypočítať štatistiku.
%s dopozeraných
@@ -410,8 +411,6 @@
\"31. 10.\" namiesto \"o 3 dni\"
Neprezrádzať dej
Skryť detaily pokiaľ nie je epizóda videná
- Zoradiť série podľa…
- Zoradiť epizódy podľa…
Preferovaný jazyk obsahu
Alternatívny jazyk
Zobrazovať obrázky výlučne cez Wi-Fi
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index d754ce8faf..b72a44623a 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -28,7 +28,6 @@
Обриши
Одбаци
- Сачувај изабрано
Прикажи све
Веб претрага
Dodaj na Home ekran
@@ -40,6 +39,7 @@
Подешавања о стримовању или куповини
Одабери регион
Ресетуј
+ Сачувај
Обриши историју претраге
Тражи епизоде
@@ -150,7 +150,8 @@
Сакривена серија
Приказана серија
Додај серију
- Филтрирај серије
+ Филтрирајте и сортирајте
+ Опште
Уклони све филтере
Омиљене
Неодгледано
@@ -161,7 +162,6 @@
Искључено
Учините све скривено видљивим
Поставите све скривене серије (%s) поново видљиве?
- Сортирај серије
Наслов
Poslednja epizoda
Najstarija epizoda
@@ -173,6 +173,8 @@
Погледана, следећа епизода
Пребацити излед
Следеће за гледање
+ Белешке
+ Уреди белешку
Историјат
Недавно гледано
@@ -244,8 +246,10 @@
Слично
Сличне емисије
-
+
+ Otkrijte
Popularno
+ Филтер
Година
Актуелна година
Језик
@@ -263,12 +267,10 @@
Уклони са листе
Уклоњено са листе
Можете додати емисије на ову листу
- Листе за сортирање
Поново сортирајте листе
Листе су преуређене
Lista sa ovim imenom već postoji
- Otkrijte
Digitalna izdanja
Disk izdanja
U bioskopima
@@ -285,11 +287,10 @@
Трејлер
Улоге
Екипа
- Сортирај филмове
Наслов
Датум емитовања
- Филтрирати филмове
Слични филмови
+ Датуми емитовања
Nije bilo moguće obraditi statistiku
%s завршио гледање
@@ -335,7 +336,7 @@
Izlogovan iz SeriesGuide Cloud-a
Грешка са пријавом (%s)
Имејл захтева пријављивање на Google
- Choose email to sign in if you are using SeriesGuide on other devices that do not support Google sign-in.
+ Изаберите имејл за пријаву ако користите SeriesGuide на другим уређајима који не подржавају пријављивање на Google.
Послато на тракт.
Пријављено гледање %s на trakt.
@@ -399,8 +400,6 @@
\"31 октобар\" у место од \"за 3 дана\"
Спречити спојлере
Сакрити детаље док се епизода не погледа
- Поређај сезоне по…
- Поређај епизоде по…
Жељени језик садржаја
Алтернативни језик
Слике само кроз бежичну мрежу
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index a159d6b706..301b3bb51e 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -28,7 +28,6 @@
Rensa
Avbryt
- Spara valda
Visa alla
Webbsökning
Lägg till på hemskärmen
@@ -40,6 +39,7 @@
Stream- eller köpinställningar
Välj region
Återställ
+ Save
Rensa sökhistorik
Sök avsnitt
@@ -149,7 +149,8 @@
Dold serie
Visar serie
Lägg till serie
- Filtrera serier
+ Filter and sort
+ General
Ta bort alla filter
Favoriter
Osedda
@@ -160,7 +161,6 @@
Exkluderad
Gör alla gömda synliga
Göra alla gömda serier (%s) synliga igen?
- Sortera serier
Titel
Senaste avsnitt
Äldsta avsnitt
@@ -172,6 +172,8 @@
Sett nästa avsnitt
Ändra vy
Härnäst
+ Notes
+ Edit note
Historik
Nyligen sedda
@@ -237,8 +239,10 @@
Liknande
Liknande program
-
+
+ Upptäck
Populärt
+ Filter
År
Nuvarande år
Språk
@@ -256,12 +260,10 @@
Ta bort från lista
Borttagen från lista
Du kan lägga till serier i denna lista
- Sortera listor
Sortera om listor
Listor omordnade
En lista med detta namn finns redan
- Upptäck
Digitala utgivningar
Utgivningar på skiva
På bio
@@ -278,11 +280,10 @@
Trailer
Rollista
Medverkande
- Sortera filmer
Titel
Utgivningsdatum
- Filtrera filmer
Liknande filmer
+ Release dates
Gick inte att beräkna statistik.
%s färdigtittade
@@ -388,8 +389,6 @@
\"31 oktober\" istället för \"om 3 dagar\"
Förhindra spoilers
Dölj detaljer tills ett avsnitt har setts
- Sortera säsonger efter…
- Sortera avsnitt efter…
Innehållsspråk som föredras
Alternativt språk
Endast bilder via Wi-Fi
diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml
index 7b66cfc542..27dae58d81 100644
--- a/app/src/main/res/values-ta/strings.xml
+++ b/app/src/main/res/values-ta/strings.xml
@@ -28,7 +28,6 @@
நீக்கு
புறக்கணி
- தேர்வு சேமி
எல்லாம் காண்பி
வலை தேடல்
Add to Home screen
@@ -40,6 +39,7 @@
Stream or purchase settings
Select region
Reset
+ Save
தேடல் வரலாற்றை நீக்கு
தேடல் episodes
@@ -149,7 +149,8 @@
நகர்த்த மறைந்த
காட்சி காணப்படும் மீண்டும்
காட்சி சேர்
- வடிகட்டி காட்சிகள்
+ Filter and sort
+ General
அனைத்து வடிகட்டிகளை நீக்கு
பிடித்தவை
கவனிக்கப்படாத
@@ -160,7 +161,6 @@
Excluded
Make all hidden visible
Make all hidden shows (%s) visible again?
- அடுக்கு காட்சிகள்
தலைப்பு
Latest episode
Oldest episode
@@ -172,6 +172,8 @@
பார்த்த அடுத்த சம்பவம்
Switch view
Next to watch
+ Notes
+ Edit note
வரலாறு
சமீபத்தில் பார்த்த
@@ -237,8 +239,10 @@
Similar
Similar shows
-
+
+ Discover
பிரபலமானவை
+ Filter
Year
Current year
Language
@@ -250,18 +254,16 @@
பட்டியலை நீக்கு
பட்டியலை நிர்வகி
List renamed
- List removed
+ List deleted
பட்டியல்களை நிர்வகி
Lists updated
பட்டியலில் இருந்து அகற்று
Removed from list
You can add shows to this list
- அடுக்கு பட்டியல்கள்
பட்டியல்களை மறு வரிசையாக்க இழுத்து
Lists reordered
இந்த பட்டியலை ஏற்கனவே உள்ளது
- Discover
Digital releases
Disc releases
In theaters
@@ -278,11 +280,10 @@
முன்னோட்டம்
நடிகர்கள்
குழுவினர்
- அடுக்கு திரைப்படங்கள்
தலைப்பு
வெளியீட்டு தேதி
- Filter movies
Similar movies
+ Release dates
புள்ளிவிவரம் கணக்கிட முடியவில்லை.
%s finished watching
@@ -388,8 +389,6 @@
\"அக்டோபர் 31\" பதிலாக \"உள்ள நாட்கள்\"
Prevent spoilers
Hide details until an episode is watched
- மூலம் அடுக்கு சீசன்ஸ்…
- மூலம் அடுக்கு episodes…
விருப்ப உள்ளடக்க மொழி
Alternative language
படிமங்கள் வழியாக Wi-Fi மட்டும்
diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml
index 8a60023277..f6fcc11d4f 100644
--- a/app/src/main/res/values-th/strings.xml
+++ b/app/src/main/res/values-th/strings.xml
@@ -28,7 +28,6 @@
ล้าง
ยกเลิก
- บันทึกที่เลือก
แสดงทั้งหมด
ค้นเว็บ
เพิ่มไปยังหน้าจอหลัก
@@ -40,6 +39,7 @@
Stream or purchase settings
เลือกภูมิภาค
Reset
+ Save
ล้างประวัติการค้นหา
ค้นหาตอน
@@ -148,7 +148,8 @@
ซ่อนรายการแล้ว
แสดงซีรีส์อีกครั้ง
เพิ่มซีรีส์
- กรองซีรีส์
+ Filter and sort
+ General
ลบตัวกรองทั้งหมด
รายการโปรด
ยังไม่ได้ดู
@@ -159,7 +160,6 @@
ไม่รวม
เลิกซ่อนรายการทั้งหมด
ทำให้รายการที่ซ่อนไว้ทั้งหมด (%s) กลับมาแสดงอีกครั้ง?
- แสดงซีรีส์
ชื่อเรื่อง
ตอนล่าสุด
ตอนเก่าที่สุด
@@ -171,6 +171,8 @@
ดูตอนถัดไป
เปลี่ยนมุมมอง
ดูถัดไป
+ Notes
+ Edit note
ประวัติ
เพิ่งดู
@@ -230,8 +232,10 @@
Similar
ซีรีส์ที่คล้ายกัน
-
+
+ สำรวจ
ยอดนิยม
+ Filter
Year
Current year
Language
@@ -243,18 +247,16 @@
ลบรายการ
จัดการรายการ
List renamed
- List removed
+ List deleted
จัดการรายการ
Lists updated
ลบออกจากรายการ
Removed from list
คุณสามารถเพิ่มซีรีส์นี้ไปยังรายการ
- เรียงรายการ
เรียงรายการใหม่
Lists reordered
มีรายการชื่อนี้อยู่แล้ว
- สำรวจ
ออกเป็นดิจิทัล
ออกเป็นแผ่น
ในโรง
@@ -271,11 +273,10 @@
ตัวอย่าง
นักแสดง
ทีมงาน
- เรียงภาพยนตร์
ชื่อเรื่อง
วันที่ออกฉาย
- กรองภาพยนตร์
Similar movies
+ Release dates
ไม่สามารถคำนวณสถิติ
%s ดูจบแล้ว
@@ -377,8 +378,6 @@
\"31 ตุลาคม\" แทน \"ใน 3 วัน\"
ป้องกันการสปอยล์
ซ่อนรายละเอียดเฉพาะตอนที่ยังไม่ได้ดู
- เรียงซีซั่นโดย…
- เรียงตอนโดย…
ภาษาของเนื้อหาที่ต้องการ
ภาษารอง
รูปภาพผ่านไวไฟเท่านั้น
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index 16118e99c5..c66416c134 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -28,7 +28,6 @@
Temizle
Yoksay
- Seçimi kaydet
Tümünü göster
Web\'de ara
Ana ekrana ekle
@@ -40,6 +39,7 @@
İzleme veya satın alma ayarları
Bölge seçin
Sıfırla
+ Kaydet
Arama geçmişini temizle
Bölüm ara
@@ -149,7 +149,8 @@
Gizlendi
Gizlenmemiş dizi
Dizi ekle
- Dizileri filtrele
+ Filtrele ve sırala
+ Genel
Tüm filtreleri kaldır
Favoriler
İzlenmemiş
@@ -160,7 +161,6 @@
Hariç tutulan
Tüm gizli öğeleri görünür yap
Tüm (%s) gizli program tekrar görünür hale gelsin mi?
- Dizileri sırala
Başlık
Son bölüme göre
En eski bölüme göre
@@ -172,6 +172,8 @@
Sıradaki bölüm izlendi
Görünümü değiştir
Sıradaki izlenecek
+ Notlar
+ Notu düzenle
Geçmiş
Yakın zamanda izlenen
@@ -237,8 +239,10 @@
Benzerleri
Benzer diziler
-
+
+ Keşfet
Popüler
+ Filtrele
Yıl
Mevcut yıl
Dil
@@ -256,12 +260,10 @@
Listeden kaldır
Listeden kaldırıldı
Bu listeye dizi ekleyebilirsiniz
- Listeyi sırala
Listeleri tekrar sırala
Listeler yeniden sıralandı
Bu ada sahip bir liste zaten var
- Keşfet
Dijital Yayınlanan
Disk\'i Yayınlanan
Vizyonda
@@ -278,11 +280,10 @@
Fragman
Oyuncular
Ekip
- Filmleri sırala
Başlık
Yayın tarihi
- Filmleri filtrele
Benzer filmler
+ Yayın tarihleri
İstatistik hesaplanamadı.
%s izlendi
@@ -388,8 +389,6 @@
\"3 gün içinde\" yerine \"31 Ekim\"
Spoiler önle
İzlenene kadar bölüm ayrıntılarını gizle
- Sezonları sırala…
- Bölümleri sırala…
Tercih edilen içerik dili
Alternatif dil
Görüntüler sadece Wi-Fi üzerinden
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 825b7fccfd..ad2931617b 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -28,7 +28,6 @@
Очистити
Відмінити
- Зберегти вибране
Показати все
Веб пошук
Додати на Домашній екран
@@ -40,6 +39,7 @@
Налаштування трансляції або купівлі
Виберіть регіон
Скинути
+ Зберегти
Очистити історію пошуку
Пошук серій
@@ -151,7 +151,8 @@
Приховані серіали
Не приховані серіали
Додати серіал
- Фільтрувати серіали
+ Фільтр і сортування
+ Типові
Видалити всі фільтри
Улюблені
Непереглянуті
@@ -162,7 +163,6 @@
Виключно
Зробити все приховане видимим
Зробити всі приховані серіали (%s) видимими знову?
- Сортувати серіали за
Назвою
Найновішою серією
Найстарішою серією
@@ -174,6 +174,8 @@
Переглянута наступна серія
Змінити вигляд
До перегляду далі
+ Нотатки
+ Змінити нотатку
Історія
Недавно переглянуті
@@ -251,8 +253,10 @@
Схожі
Подібні серіали
-
+
+ Дослідити
Популярне
+ Фільтри
Рік
Поточний рік
Мова
@@ -270,12 +274,10 @@
Видалити зі списку
Усунено зі списку
Ви можете додати серіали в цей список
- Сортувати списки
Впорядкувати список
Список перевпорядковано
Список з таким іменем вже існує
- Дослідити
Цифрові видання
Видання на диску
В театрах
@@ -292,11 +294,10 @@
Трейлер
У ролях
Знімальна група
- Сортувати фільми
Назвою
Датою виходу
- Фільтрувати фільми
Схожі фільми
+ Дати випусків
Не вдалося підрахувати статистику
%s завершених переглядів
@@ -410,8 +411,6 @@
\"31 жовтня\" замість \" 3 дні\"
Запобігання спойлерам
Приховати подробиці до перегляду епізоду
- Сортувати сезони за…
- Сортувати серії за…
Бажана мова контенту
Альтернативна мова
Зображення лише через Wi-Fi
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 0a938f8eca..2de956d5b9 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -28,7 +28,6 @@
清空
放弃
- 保存已选项
显示全部
网页搜索
添加到主屏幕
@@ -40,6 +39,7 @@
Stream or purchase settings
选择地区
重置
+ Save
清除搜索记录
搜索剧集
@@ -148,7 +148,8 @@
已隐藏该节目
已重新显示该节目
添加节目
- 筛选节目
+ Filter and sort
+ General
移除所有筛选条件
已标记为喜爱
未观看
@@ -159,7 +160,6 @@
已排除
显示所有隐藏的节目
重新显示所有隐藏的节目(共 %s 个)吗?
- 排序节目
按片名
按最新一集
按最早一集
@@ -171,6 +171,8 @@
标记本集为看过
切换视图
接下来要看的一集
+ Notes
+ Edit note
历史记录
最近看过
@@ -230,8 +232,10 @@
相似
相似节目
-
+
+ 发现
热门
+ Filter
年份
本年
语言
@@ -249,12 +253,10 @@
从列表中移除
已从列表中删除
您可以添加节目到此列表
- 排序列表
对列表重新排序
列表已重新排序
已存在同名列表
- 发现
数字发行
光盘发行
上映中
@@ -271,11 +273,10 @@
预告片
演员
工作人员
- 排序电影
按片名
按发行日期
- 筛选电影
相似电影
+ Release dates
无法计算统计数据
看完了 %s 个节目
@@ -377,8 +378,6 @@
如显示为「10 月 31 日」而不是「3 天后」
防止剧透
隐藏未观看剧集的详细信息
- 季排序方式…
- 集排序方式…
内容首选的语言
副语言
仅通过 Wi-Fi 下载图片
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 0979b30fd5..a7c7cd1312 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -28,7 +28,6 @@
清除
放棄
- 儲存所選項目
顯示全部
網頁搜尋
新增至主螢幕
@@ -40,6 +39,7 @@
Stream or purchase settings
選擇地區
Reset
+ Save
清除搜尋記錄
搜尋集數
@@ -148,7 +148,8 @@
隱藏影集
取消隱藏影集
新增節目
- 篩選節目
+ Filter and sort
+ General
重設篩選條件
最喜愛的節目
尚未觀看
@@ -159,7 +160,6 @@
已排除
顯示所有隱藏的節目
Make all hidden shows (%s) visible again?
- 排序節目
按節目名稱
按最新一集
按最舊一集
@@ -171,6 +171,8 @@
觀看下一集
切換檢視模式
Next to watch
+ Notes
+ Edit note
歴史記錄
最近觀賞
@@ -230,8 +232,10 @@
Similar
類似節目
-
+
+ 探索
熱門
+ Filter
Year
Current year
Language
@@ -243,18 +247,16 @@
移除清單
管理清單
List renamed
- List removed
+ List deleted
管理清單
Lists updated
從清單中移除
Removed from list
您可以新增節目到此列表
- 排序清單
重新排序清單
Lists reordered
此名稱的清單已存在。
- 探索
數位發行
碟片版本
上映中
@@ -271,11 +273,10 @@
預告
卡司
工作人員
- 電影排序
按節目名稱
播出日期
- 篩選電影
Similar movies
+ Release dates
無法統計資料。
看完了 %s 個節目
@@ -377,8 +378,6 @@
例如:「10月31日」而非「3 天後」
避免劇透
隱藏詳細資訊直到看過一集
- 以…排序每一季
- 以…排序每一集
偏好的語言
替代語言
僅透過 Wi-Fi 載入圖片
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8428dcd237..559b7b2afb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -32,7 +32,6 @@
Clear
Discard
- Save selection
Show all
Web search
Add to Home screen
@@ -44,6 +43,7 @@
Stream or purchase settings
Select region
Reset
+ Save
Clear search history
@@ -161,7 +161,8 @@
Moved to hidden
Show is visible again
Add show
- Filter shows
+ Filter and sort
+ General
Reset filters
Favorites
Unwatched
@@ -172,7 +173,6 @@
Excluded
Make all hidden visible
Make all hidden shows (%s) visible again?
- Sort shows
Title
Latest episode
Oldest episode
@@ -184,6 +184,8 @@
Watched next episode
Switch view
Next to watch
+ Notes
+ Edit note
History
@@ -256,8 +258,10 @@
Similar
Similar shows
-
+
+ Discover
Popular
+ Filter
Year
Current year
Language
@@ -267,22 +271,20 @@
Add list
List added
Name your list
- Remove list
+ Delete list
Manage list
List renamed
- List removed
+ List deleted
Manage lists
Lists updated
Remove from list
Removed from list
You can add shows to this list
- Sort lists
Reorder lists
Lists reordered
A list with this name already exists
- Discover
Digital releases
Disc releases
In theaters
@@ -299,11 +301,10 @@
Trailer
Cast
Crew
- Sort movies
Title
Release date
- Filter movies
Similar movies
+ Release dates
Could not calculate statistics
@@ -419,8 +420,6 @@
\"October 31\" instead of \"in 3 days\"
Prevent spoilers
Hide details until an episode is watched
- Sort seasons by…
- Sort episodes by…
Preferred content language
Alternative language
Images via Wi-Fi only
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 62c0e8ccf3..9b7520e0cc 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -69,11 +69,6 @@
- ?attr/sgTextColorSecondaryDim
-
-
+
+
diff --git a/app/src/test/java/com/battlelancer/seriesguide/dataliberation/JsonExportTaskTest.kt b/app/src/test/java/com/battlelancer/seriesguide/dataliberation/JsonExportTaskTest.kt
index aa182af4b2..980a8e8b2d 100644
--- a/app/src/test/java/com/battlelancer/seriesguide/dataliberation/JsonExportTaskTest.kt
+++ b/app/src/test/java/com/battlelancer/seriesguide/dataliberation/JsonExportTaskTest.kt
@@ -153,7 +153,9 @@ class JsonExportTaskTest {
posterSmall = "someurl/to/a/poster.jpg",
language = "de", // Use language different from default (English).
lastUpdatedMs = 0,
- lastWatchedMs = 1234567890
+ lastWatchedMs = 1234567890,
+ userNote = "This is an example note 😀",
+ userNoteTraktId = 12345
),
SgShow2(
id = 2,
@@ -174,12 +176,14 @@ class JsonExportTaskTest {
ratingTrakt = null,
ratingTraktVotes = null,
ratingUser = null,
- lastUpdatedMs = 0
+ lastUpdatedMs = 0,
+ userNote = null,
+ userNoteTraktId = null
)
)
@Language("json")
const val expectedJsonShows =
- """[{"tmdb_id":95479,"imdb_id":"imdbidvalue","trakt_id":52,"title":"Jujutsu Kaisen","overview":"It\u0027s all about hollow purple.","language":"de","first_aired":"2021-02-27T05:11:12.345Z","release_time":1234,"release_weekday":1,"release_timezone":"America/New_York","country":"JP","custom_release_time":1215,"custom_release_day_offset":28,"custom_release_timezone":"Europe/Berlin","poster":"someurl/to/a/poster.jpg","content_rating":"","status":"ended","runtime":24,"genres":"Animation|Action \u0026 Adventure|Sci-Fi \u0026 Fantasy","network":"MBS","rating_tmdb":2.4,"rating_tmdb_votes":4321,"rating":10.0,"rating_votes":1234,"rating_user":4,"favorite":false,"notify":true,"hidden":false,"last_watched_ms":1234567890,"seasons":[{"tmdb_id":"1","season":1,"episodes":[{"tmdb_id":1,"episode":1,"title":"First Episode","first_aired":1234567890,"watched":true,"plays":1,"skipped":false,"collected":false,"imdb_id":"","overview":"First overview","image":"/first/still/path.jpg","writers":"writers string","gueststars":"guest stars string","directors":"directors string","rating_tmdb":2.4,"rating_tmdb_votes":4321,"rating":10.0,"rating_votes":1234,"rating_user":4},{"tmdb_id":2,"episode":2,"title":"Second Episode","first_aired":1234567890,"watched":false,"plays":0,"skipped":true,"collected":true,"imdb_id":"","overview":"Second overview","image":"/first/still/path.jpg","writers":"writers string","gueststars":"guest stars string","directors":"directors string"}]},{"tmdb_id":"2","season":2,"episodes":[{"tmdb_id":1,"episode":1,"title":"First Episode","first_aired":1234567890,"watched":true,"plays":1,"skipped":false,"collected":false,"imdb_id":"","overview":"First overview","image":"/first/still/path.jpg","writers":"writers string","gueststars":"guest stars string","directors":"directors string","rating_tmdb":2.4,"rating_tmdb_votes":4321,"rating":10.0,"rating_votes":1234,"rating_user":4},{"tmdb_id":2,"episode":2,"title":"Second Episode","first_aired":1234567890,"watched":false,"plays":0,"skipped":true,"collected":true,"imdb_id":"","overview":"Second overview","image":"/first/still/path.jpg","writers":"writers string","gueststars":"guest stars string","directors":"directors string"}]}]},{"imdb_id":"","title":"","overview":"","language":"","release_time":-1,"release_weekday":-1,"release_timezone":"","poster":"","content_rating":"","status":"unknown","runtime":0,"genres":"","network":"","favorite":false,"notify":true,"hidden":false,"last_watched_ms":0,"seasons":[]}]"""
+ """[{"tmdb_id":95479,"imdb_id":"imdbidvalue","trakt_id":52,"title":"Jujutsu Kaisen","overview":"It\u0027s all about hollow purple.","language":"de","first_aired":"2021-02-27T05:11:12.345Z","release_time":1234,"release_weekday":1,"release_timezone":"America/New_York","country":"JP","custom_release_time":1215,"custom_release_day_offset":28,"custom_release_timezone":"Europe/Berlin","poster":"someurl/to/a/poster.jpg","content_rating":"","status":"ended","runtime":24,"genres":"Animation|Action \u0026 Adventure|Sci-Fi \u0026 Fantasy","network":"MBS","rating_tmdb":2.4,"rating_tmdb_votes":4321,"rating":10.0,"rating_votes":1234,"rating_user":4,"favorite":false,"notify":true,"hidden":false,"last_watched_ms":1234567890,"user_note":"This is an example note 😀","user_note_trakt_id":12345,"seasons":[{"tmdb_id":"1","season":1,"episodes":[{"tmdb_id":1,"episode":1,"title":"First Episode","first_aired":1234567890,"watched":true,"plays":1,"skipped":false,"collected":false,"imdb_id":"","overview":"First overview","image":"/first/still/path.jpg","writers":"writers string","gueststars":"guest stars string","directors":"directors string","rating_tmdb":2.4,"rating_tmdb_votes":4321,"rating":10.0,"rating_votes":1234,"rating_user":4},{"tmdb_id":2,"episode":2,"title":"Second Episode","first_aired":1234567890,"watched":false,"plays":0,"skipped":true,"collected":true,"imdb_id":"","overview":"Second overview","image":"/first/still/path.jpg","writers":"writers string","gueststars":"guest stars string","directors":"directors string"}]},{"tmdb_id":"2","season":2,"episodes":[{"tmdb_id":1,"episode":1,"title":"First Episode","first_aired":1234567890,"watched":true,"plays":1,"skipped":false,"collected":false,"imdb_id":"","overview":"First overview","image":"/first/still/path.jpg","writers":"writers string","gueststars":"guest stars string","directors":"directors string","rating_tmdb":2.4,"rating_tmdb_votes":4321,"rating":10.0,"rating_votes":1234,"rating_user":4},{"tmdb_id":2,"episode":2,"title":"Second Episode","first_aired":1234567890,"watched":false,"plays":0,"skipped":true,"collected":true,"imdb_id":"","overview":"Second overview","image":"/first/still/path.jpg","writers":"writers string","gueststars":"guest stars string","directors":"directors string"}]}]},{"imdb_id":"","title":"","overview":"","language":"","release_time":-1,"release_weekday":-1,"release_timezone":"","poster":"","content_rating":"","status":"unknown","runtime":0,"genres":"","network":"","favorite":false,"notify":true,"hidden":false,"last_watched_ms":0,"seasons":[]}]"""
val listOfTestSeasons = listOf(
SgSeason2(
diff --git a/app/src/test/java/com/battlelancer/seriesguide/util/ImageToolsTest.kt b/app/src/test/java/com/battlelancer/seriesguide/util/ImageToolsTest.kt
index a8e2ac81de..1ac09951ab 100644
--- a/app/src/test/java/com/battlelancer/seriesguide/util/ImageToolsTest.kt
+++ b/app/src/test/java/com/battlelancer/seriesguide/util/ImageToolsTest.kt
@@ -1,11 +1,12 @@
-// Copyright 2023 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2021-2024 Uwe Trottmann
package com.battlelancer.seriesguide.util
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.battlelancer.seriesguide.EmptyTestApplication
+import com.battlelancer.seriesguide.settings.TmdbSettings
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
@@ -20,15 +21,51 @@ class ImageToolsTest {
@Test
fun posterUrl() {
+ assertImageUrl(
+ TmdbSettings.POSTER_SIZE_SPEC_W154,
+ tmdbUrlBuilder = { path ->
+ ImageTools.tmdbOrTvdbPosterUrl(path, context)
+ },
+ tmdbUrlOriginalBuilder = { path ->
+ ImageTools.tmdbOrTvdbPosterUrl(path, context, true)
+ },
+ tvdbUrlBuilder = { path ->
+ ImageTools.tmdbOrTvdbPosterUrl(path, context)
+ }
+ )
+ }
+
+ @Test
+ fun episodeImageUrl() {
+ assertImageUrl(
+ TmdbSettings.BACKDROP_SMALL_SIZE_SPEC,
+ tmdbUrlBuilder = { path ->
+ ImageTools.buildEpisodeImageUrl(path, context)
+ },
+ tmdbUrlOriginalBuilder = { path ->
+ ImageTools.buildEpisodeImageUrl(path, context, originalSize = true)
+ },
+ tvdbUrlBuilder = { path ->
+ ImageTools.buildEpisodeImageUrl(path, context)
+ }
+ )
+ }
+
+ private fun assertImageUrl(
+ smallSize: String,
+ tmdbUrlBuilder: (String) -> String?,
+ tmdbUrlOriginalBuilder: (String) -> String?,
+ tvdbUrlBuilder: (String) -> String?
+ ) {
// Note: TMDB image paths start with / whereas TVDB paths do not.
- val tmdbUrl = ImageTools.tmdbOrTvdbPosterUrl("/example.jpg", context)
- val tmdbUrlOriginal = ImageTools.tmdbOrTvdbPosterUrl("/example.jpg", context, true)
- val tvdbUrl = ImageTools.tmdbOrTvdbPosterUrl("posters/example.jpg", context)
+ val tmdbUrl = tmdbUrlBuilder("/example.jpg")
+ val tmdbUrlOriginal = tmdbUrlOriginalBuilder("/example.jpg")
+ val tvdbUrl = tvdbUrlBuilder("posters/example.jpg")
println("TMDB URL: $tmdbUrl")
println("TMDB original URL: $tmdbUrlOriginal")
println("TVDB URL: $tvdbUrl")
assertThat(tmdbUrl).isNotEmpty()
- assertThat(tmdbUrl).endsWith("https://image.tmdb.org/t/p/w154/example.jpg")
+ assertThat(tmdbUrl).endsWith("https://image.tmdb.org/t/p/$smallSize/example.jpg")
assertThat(tmdbUrlOriginal).isNotEmpty()
assertThat(tmdbUrlOriginal).endsWith("https://image.tmdb.org/t/p/original/example.jpg")
assertThat(tvdbUrl).isNotEmpty()
diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts
index 1b863719f2..f06c80626d 100644
--- a/backend/build.gradle.kts
+++ b/backend/build.gradle.kts
@@ -7,6 +7,11 @@ plugins {
val sgCompileSdk: Int by rootProject.extra
val sgMinSdk: Int by rootProject.extra
+tasks.withType(JavaCompile::class.java).configureEach {
+ // Suppress JDK 21 warning about deprecated, but not yet removed, source and target value 8 support
+ options.compilerArgs.add("-Xlint:-options")
+}
+
android {
namespace = "com.uwetrottmann.seriesguide.backend"
compileSdk = sgCompileSdk
diff --git a/billing/build.gradle.kts b/billing/build.gradle.kts
index ac4ef3c61e..dc65c3acb7 100644
--- a/billing/build.gradle.kts
+++ b/billing/build.gradle.kts
@@ -7,6 +7,11 @@ plugins {
val sgCompileSdk: Int by rootProject.extra
val sgMinSdk: Int by rootProject.extra
+tasks.withType(JavaCompile::class.java).configureEach {
+ // Suppress JDK 21 warning about deprecated, but not yet removed, source and target value 8 support
+ options.compilerArgs.add("-Xlint:-options")
+}
+
android {
namespace = "com.uwetrottmann.seriesguide.billing"
compileSdk = sgCompileSdk
@@ -50,6 +55,8 @@ dependencies {
implementation(libs.androidx.lifecycle.viewmodel)
// Room
implementation(libs.androidx.room.runtime)
+ // KSP appears deprecated. KSP 2 is still under development.
+ //noinspection KaptUsageInsteadOfKsp
kapt(libs.androidx.room.compiler)
implementation(libs.timber)
diff --git a/build.gradle.kts b/build.gradle.kts
index d9b3c4d795..25e1c90906 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,6 +4,7 @@ plugins {
// app, libraries
alias(libs.plugins.android) apply false
alias(libs.plugins.kotlin) apply false
+ alias(libs.plugins.compose.compiler) apply false
// Firebase Crashlytics
alias(libs.plugins.google.services) apply false
alias(libs.plugins.firebase.crashlytics) apply false
@@ -14,16 +15,16 @@ plugins {
}
buildscript {
- val sgCompileSdk by extra(34) // Android 14 (UPSIDE_DOWN_CAKE)
+ val sgCompileSdk by extra(35) // Android 15 (VANILLA_ICE_CREAM)
val sgMinSdk by extra(21) // Android 5 (L)
val sgTargetSdk by extra(34) // Android 14 (UPSIDE_DOWN_CAKE)
// YYYY.. - like 2024.1.0
// - allows to more easily judge how old a release is
// - allows multiple releases per month (though currently unlikely)
- val sgVersionName by extra("2024.4.6")
+ val sgVersionName by extra("2024.5.4")
// version 21yyrrbb -> min SDK 21, year yy, release rr, build bb
- val sgVersionCode by extra(21240407)
+ val sgVersionCode by extra(21240505)
val isCiBuild by extra { System.getenv("CI") == "true" }
diff --git a/docs/CODING_GUIDELINES.md b/docs/CODING_GUIDELINES.md
deleted file mode 100644
index d0c0f234a4..0000000000
--- a/docs/CODING_GUIDELINES.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Coding Guidelines
-
-Collecting design decisions. New and updated code should follow them.
-
-## Layout resources
-
-View IDs should be unique across the project to support refactoring using Android Studio.
-
-Example: `textViewItemEpisodeTitle` in `item_episode.xml`
-
-Use dimension resources (like `@dimen/default_padding`) for margin and padding to avoid looking them up.
-
-## Click listeners
-
-The interface class is owned by the class that owns the views that trigger the click events, for
-example the item view holder.
-
-The interface class is named based on what the listener is for.
-
-Example: `ItemClickListener`
-
-The methods are named based on what is clicked.
-
-Example: `onMoreOptionsClick`.
-
-## PopupMenu
-
-Use `androidx.appcompat.widget.PopupMenu` so Material 3 styles are correctly applied.
-(Could also use the platform one and set Widget.Material3.PopupMenu with android:popupMenuStyle,
-but rather use the same implementation on all versions.)
diff --git a/docs/backup-json-schema.md b/docs/backup-json-schema.md
new file mode 100644
index 0000000000..4866c29836
--- /dev/null
+++ b/docs/backup-json-schema.md
@@ -0,0 +1,137 @@
+# Backup JSON schema
+
+_SeriesGuide can [export and import your data](http://seriesgui.de/help#backup) using JSON text files. Their schema is documented here._
+
+The actual exported JSON is written without indents and comments to save space and increase performance. The following representation is for documentation purposes only.
+
+When importing, not all but a few required values need to be present. SeriesGuide will in most cases fill in missing values on the next update.
+
+## Shows
+
+Based on the
+
+- [Show](/app/src/main/java/com/battlelancer/seriesguide/dataliberation/model/Show.java)
+- [Season](/app/src/main/java/com/battlelancer/seriesguide/dataliberation/model/Season.java)
+- [Episode](/app/src/main/java/com/battlelancer/seriesguide/dataliberation/model/Episode.java)
+
+classes.
+
+```json
+[
+ {
+ "content_rating": "MA",
+ "country": "us", // two letter ISO 3166-1 alpha-2 country code
+ "favorite": true,
+ "first_aired": "2018-02-02T08:00:00Z", // ISO 8601 datetime string
+ "hidden": false,
+ "imdb_id": "tt2261227",
+ "language": "en",
+ "last_watched_ms": 1614593199175,
+ "network": "Netflix",
+ "notify": true,
+ "poster": "/95IsiH4p5937YXQHaOS2W2dWYOG.jpg", // TMDb poster path
+ "rating": 9.5, // 0.0 to 10.0
+ "rating_user": 10, // 0, 1 to 10
+ "rating_votes": 100,
+ "release_time": 300, // Encoded 24 hour local time (hhmm)
+ "release_timezone": "America/New_York", // tz database name (Olson)
+ "release_weekday": 4, // Local release week day (1-7, 0 if daily, -1 if unknown)
+ "runtime": 50, // in minutes
+ "seasons": [
+ {
+ "episodes": [
+ {
+ "collected": false,
+ "episode": 1,
+ "first_aired": 1517558400000, // ms
+ "imdb_id": "",
+ "plays": 1,
+ "skipped": false,
+ "title": "Out of the Past",
+ "tmdb_id": 1401623,
+ "watched": true
+ }
+ ],
+ "season": 1,
+ "tmdb_id": "81447"
+ }
+ ],
+ "status": "canceled", // see JsonExportTask.ShowStatusExport
+ "title": "Altered Carbon",
+ "tmdb_id": 68421, // required, or set linked tvdb_id
+ "trakt_id": 122265,
+ "tvdb_id": 332331 // if tmdb_id not set, used to look it up
+ }
+]
+```
+
+## Lists
+
+Based on the
+
+- [List](/app/src/main/java/com/battlelancer/seriesguide/dataliberation/model/List.java)
+- [ListItem](/app/src/main/java/com/battlelancer/seriesguide/dataliberation/model/ListItem.java)
+
+classes.
+
+```json
+[
+ {
+ "items": [
+ {
+ "externalId": "62425", // TMDB ID
+ "list_item_id": "62425-4-firstlist",
+ "tvdb_id": 0, // unused
+ "type": "tmdb-show"
+ },
+ // type episode, season and show are legacy items,
+ // are only displayed if externalId matches a TVDB ID in SeriesGuide library,
+ // so does not work for new shows added to library
+ {
+ "externalId": "5443955",
+ "list_item_id": "5443955-3-firstlist",
+ "tvdb_id": 0,
+ "type": "episode"
+ },
+ {
+ "externalId": "620558",
+ "list_item_id": "620558-2-firstlist",
+ "tvdb_id": 0,
+ "type": "season"
+ },
+ {
+ "externalId": "253491",
+ "list_item_id": "253491-1-firstlist",
+ "tvdb_id": 0,
+ "type": "show"
+ }
+ ],
+ "list_id": "firstlist",
+ "name": "First list",
+ "order": 2
+ }
+]
+```
+
+## Movies
+
+Based on the [Movie](/app/src/main/java/com/battlelancer/seriesguide/dataliberation/model/Movie.java) class.
+
+```json
+[
+ {
+ "imdb_id": "tt1022603",
+ "in_collection": false,
+ "in_watchlist": false,
+ "last_updated_ms": 1619620588554,
+ "overview": "Some text.",
+ "plays": 1,
+ "poster": "/f9mbM0YMLpYemcWx6o2WeiYQLDP.jpg", // TMDB poster path
+ "released_utc_ms": 1256162400000,
+ "runtime_min": 95,
+ "title": "(500) Days of Summer",
+ "tmdb_id": 19913,
+ "watched": true
+ }
+]
+```
diff --git a/docs/guidelines.md b/docs/guidelines.md
new file mode 100644
index 0000000000..3a6ab1a7f1
--- /dev/null
+++ b/docs/guidelines.md
@@ -0,0 +1,112 @@
+# Guidelines
+
+Collecting design decisions. New and updated code and resources should follow them.
+
+## Kotlin
+
+For function calls, specify names of function parameters when the name of the passed value does not
+make it obvious:
+
+```kotlin
+// DO
+setVisible(true)
+doSomething(avoidWork = true)
+// AVOID
+doSomething(true)
+```
+
+## Layout resources
+
+View IDs should be unique across the project to support refactoring using Android Studio.
+
+Example: `textViewItemEpisodeTitle` in `item_episode.xml`
+
+Use dimension resources (like `@dimen/default_padding`) for margin and padding to avoid looking them up.
+
+### Icons
+
+Use [Material Icons](https://fonts.google.com/icons) with
+
+- **Rounded** style
+- weight 400
+- no grade
+- typically 24dp size
+
+Some existing icons may still use the old Filled or the old non-rounded Outlined style.
+
+Name icon resource files like `ic___dp.xml`,
+for example `ic_event_control_24dp.xml`.
+
+Load vector drawables [using compat loading](https://medium.com/androiddevelopers/using-vector-assets-in-android-apps-4318fd662eb9)
+so they work (tinting) and do not crash (gradients) on all supported releases:
+
+- `Button`: use `ViewTools.setVectorDrawableTop`, ... (uses `AppCompatResources.getDrawable()`)
+- `ImageView`, `ImageButton`: use `app:srcCompat`
+- `TextView`: use `app:drawableStartCompat`, `app:drawableTopCompat`, ...
+- **Optionally** if the drawable is just a color
+- **Not** for app widget layouts as the system initializes them
+
+## Click listeners
+
+The interface class is owned by the class that owns the views that trigger the click events, for
+example the item view holder.
+
+The interface class is named based on what the listener is for.
+
+Example: `ItemClickListener`
+
+The methods are named based on what is clicked.
+
+Example: `onMoreOptionsClick`.
+
+## Dialogs
+
+Using `AppCompatDialogFragment` and overriding `onCreateView` will use `dialogTheme` of the theme.
+
+If possible, use an alert dialog with a custom layout instead for improved sizing, easy adding of title and buttons.
+
+## Alert dialogs (recommended)
+
+Using `AppCompatDialogFragment` and overriding `onCreateDialog` with `MaterialAlertDialogBuilder`.
+
+The dialog theme is `materialAlertDialogTheme`, which only sets `android:windowMinWidthMajor` and `android:windowMinWidthMinor`.
+
+The layout used is `abc_alert_dialog_material.xml` defined via `alertDialogStyle` of the theme.
+
+When using a custom layout via `setView()`:
+
+- `layout_width` and `layout_height` of the root view are overwritten to `match_parent` (see `androidx.appcompat.app.AlertController#setupCustomContent`)
+- its parents use `layout_width="match_parent"`, `layout_height="wrap_content"`, `minHeight="48dp"` (see `abc_alert_dialog_material.xml`)
+- its `customPanel` parent is resized to take only as much space as is available (see `AlertDialogLayout`)
+
+## PopupMenu
+
+Use `androidx.appcompat.widget.PopupMenu` so Material 3 styles are correctly applied.
+(Could also use the platform one and set Widget.Material3.PopupMenu with android:popupMenuStyle,
+but rather use the same implementation on all versions.)
+
+## TextInputLayout
+
+When trying to make `TextInputLayout` (grow to) fill available height, if the contained `TextInputEditText` is too tall the counter or error text can get pushed outside of its bounds.
+
+As it is a `LinearLayout`, [until this is fixed](https://github.com/material-components/material-components-android/issues/1435) resolve by using `android:layout_weight="1"` on the contained `TextInputEditText`:
+
+```xml
+
+
+
+
+
+
+```
diff --git a/docs/legacy-versions.md b/docs/legacy-versions.md
new file mode 100644
index 0000000000..5d8640e09d
--- /dev/null
+++ b/docs/legacy-versions.md
@@ -0,0 +1,53 @@
+# Legacy versions
+
+## Android 4.4, 4.3, 4.2, 4.1
+
+🚨 This version may stop working at any point as it is using the old TheTVDB API.
+
+**Latest version:** 46.7-k (2021-09-23)
+
+**Available on Google Play:** [yes, get it on Google Play](https://play.google.com/store/apps/details?id=com.battlelancer.seriesguide)
+
+**Supported:** no (since May 2019)
+
+[Get the APK](https://github.com/UweTrottmann/SeriesGuide/releases/tag/v46.7-k)
+
+## Android 4.0.3
+
+**Latest version:** 46.4-k (2019-05-02)
+
+**Available on Google Play:** no
+
+**Supported:** no (since May 2019)
+
+[Get the APK](https://github.com/UweTrottmann/SeriesGuide/releases/tag/v46.4-k)
+
+## Android 3.0 and older
+
+_Since August 2014, SeriesGuide is available through Google Play or Amazon App Store only on Android 4.0.3 (Ice Cream Sandwich) and up. If you are still running Android 3.x (Honeycomb) or 2.3.x (Gingerbread), you can get a limited version of SeriesGuide here by downloading the Android application package (APK) file._
+
+_**Note: these versions will stop working in the near future as TheTVDB is plannning to shut down their old API.**_
+
+### Android 3.0
+
+**Latest version:** 14.1.0-hc (2015-08-27)
+
+**Available on Google Play:** no
+
+**Supported:** no (since August 2014)
+
+**Status:** no trakt support as of August 2015 (trakt.tv legacy interface was turned off)
+
+[Get the APK](https://github.com/UweTrottmann/SeriesGuide/releases/tag/v14.1.0-hc)
+
+### Android 2.3
+
+**Latest version:** 13.1.0-gb (2015-08-27)
+
+**Available on Google Play:** no
+
+**Supported:** no (since August 2014)
+
+**Status:** no trakt support as of August 2015 (trakt.tv legacy interface was turned off)
+
+[Get the APK](https://github.com/UweTrottmann/SeriesGuide/releases/tag/v13.1.0-gb)
diff --git a/docs/random-facts.md b/docs/random-facts.md
new file mode 100644
index 0000000000..0bd942e495
--- /dev/null
+++ b/docs/random-facts.md
@@ -0,0 +1,38 @@
+# Random facts
+
+**SeriesGuide was first published to Google Play** on 2010-09-20.
+
+The SeriesGuide code was **originally hosted** on [Google Code](http://code.google.com/).
+
+The **[GitHub repo was created](https://api.github.com/repos/UweTrottmann/SeriesGuide)** on 2011-07-03.
+
+**First commit on GitHub:**
+
+```text
+commit febe4370deac71e9545823b0eb6fe9631ee77fad
+Author: Uwe Trottmann
+Date: Sun Jul 3 12:31:21 2011 +0200
+
+ Import project files of SeriesGuide and ActionBarSherlock.
+```
+
+The **42nd commit** fixed a `NullPointerException` :)
+
+```text
+commit 9880f8da9ca42675eb4449fb60a95f4a6c5490f5
+Author: Uwe Trottmann
+Date: Sat Jul 23 18:02:28 2011 +0200
+
+ Fix market reported crash (Exception class
+ java.lang.NullPointerException, Source method TheTVDB.fetchArt())
+```
+
+The **first contributed commit** was from Jake Wharton.
+
+```text
+commit a7f18686abb09712f5ff72999aa486afbf02f531
+Author: Jake Wharton
+Date: Tue Jul 26 11:55:37 2011 -0400
+
+ Add preference toggle for showing only episodes which occur in a season (i.e., are not specials) for the "next" field.
+```
diff --git a/gradle.properties b/gradle.properties
index adb4adeaf2..cd778232ac 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -17,3 +17,6 @@ android.useAndroidX=true
# Do not turn on full mode for as long as possible: looking at unused.txt diff there are many
# differences with an increased risk of breaking things.
android.enableR8.fullMode=false
+
+# JDK 21 still supports source and target value 8, it will be removed in a future release
+android.javaCompile.suppressSourceTargetDeprecationWarning=true
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 66fcb93a38..d6b1e56939 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,7 +1,7 @@
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
[versions]
-androidx-lifecycle = "2.8.4" # https://developer.android.com/jetpack/androidx/releases/lifecycle
+androidx-lifecycle = "2.8.7" # https://developer.android.com/jetpack/androidx/releases/lifecycle
debugdrawer = "0.9.0" # https://github.com/lenguyenthanh/DebugDrawer
eventbus = "3.3.1"
google-api-client = "2.6.0" # https://github.com/googleapis/google-api-java-client/releases
@@ -11,28 +11,28 @@ retrofit2 = "2.11.0" # https://github.com/square/retrofit/blob/master/CHANGELOG.
amazon-appstore-sdk = "com.amazon.device:amazon-appstore-sdk:3.0.5" # https://developer.amazon.com/docs/in-app-purchasing/iap-whats-new.html
androidutils = "com.uwetrottmann.androidutils:androidutils:4.0.0" # https://github.com/UweTrottmann/AndroidUtils/releases
# https://developer.android.com/jetpack/androidx/releases/activity
-androidx-activity = "androidx.activity:activity:1.9.1"
+androidx-activity = "androidx.activity:activity:1.9.3"
# https://developer.android.com/jetpack/androidx/releases/annotation
-androidx-annotation = "androidx.annotation:annotation:1.8.0"
+androidx-annotation = "androidx.annotation:annotation:1.9.1"
# https://developer.android.com/jetpack/androidx/releases/appcompat
androidx-appcompat = "androidx.appcompat:appcompat:1.7.0"
# https://developer.android.com/jetpack/androidx/releases/browser
androidx-browser = "androidx.browser:browser:1.8.0"
# https://developer.android.com/jetpack/androidx/releases/collection
-androidx-collection = "androidx.collection:collection:1.4.2"
-androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" # https://developer.android.com/jetpack/androidx/releases/constraintlayout
+androidx-collection = "androidx.collection:collection:1.4.5"
+androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.2.0" # https://developer.android.com/jetpack/androidx/releases/constraintlayout
androidx-coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:1.2.0" # https://developer.android.com/jetpack/androidx/releases/coordinatorlayout
# https://developer.android.com/jetpack/androidx/releases/core
-androidx-core = "androidx.core:core:1.13.1"
-androidx-core-ktx = "androidx.core:core-ktx:1.13.1"
+androidx-core = "androidx.core:core:1.15.0"
+androidx-core-ktx = "androidx.core:core-ktx:1.15.0"
# https://developer.android.com/jetpack/androidx/releases/fragment
-androidx-fragment = "androidx.fragment:fragment-ktx:1.8.2"
+androidx-fragment = "androidx.fragment:fragment-ktx:1.8.5"
androidx-lifecycle-livedata = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" }
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
# https://developer.android.com/jetpack/androidx/releases/paging
# 3.3.0 and 3.3.1 crash when the data is refreshed while scrolling ("Inconsistency detected.")
-# https://issuetracker.google.com/issues/343124454
+# https://issuetracker.google.com/issues/381024738
androidx-paging = "androidx.paging:paging-runtime:3.2.1"
androidx-palette = "androidx.palette:palette-ktx:1.0.0"
# https://developer.android.com/jetpack/androidx/releases/preference
@@ -105,30 +105,38 @@ retrofit2-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref
retrofit2 = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit2" }
# https://github.com/robolectric/robolectric/releases/
robolectric = "org.robolectric:robolectric:4.13"
-threetenabp = "com.jakewharton.threetenabp:threetenabp:1.4.7" # https://github.com/JakeWharton/ThreeTenABP/blob/master/CHANGELOG.md
+threetenabp = "com.jakewharton.threetenabp:threetenabp:1.4.8" # https://github.com/JakeWharton/ThreeTenABP/blob/master/CHANGELOG.md
timber = "com.jakewharton.timber:timber:5.0.1" # https://github.com/JakeWharton/timber/blob/master/CHANGELOG.md
tmdb-java = "com.uwetrottmann.tmdb2:tmdb-java:2.11.0" # https://github.com/UweTrottmann/tmdb-java/blob/main/CHANGELOG.md
-trakt-java = "com.uwetrottmann.trakt5:trakt-java:6.14.0" # https://github.com/UweTrottmann/trakt-java/blob/main/CHANGELOG.md
+trakt-java = "com.uwetrottmann.trakt5:trakt-java:6.16.0" # https://github.com/UweTrottmann/trakt-java/blob/main/CHANGELOG.md
truth = "com.google.truth:truth:1.4.4" # https://github.com/google/truth/releases
# Jetpack Compose
# https://developer.android.com/jetpack/compose/setup
# https://developer.android.com/jetpack/compose/bom/bom-mapping
# https://developer.android.com/jetpack/androidx/releases/compose
-compose = "androidx.compose:compose-bom:2024.06.00"
+compose = "androidx.compose:compose-bom:2024.11.00"
# https://developer.android.com/jetpack/androidx/releases/compose-material3
compose-material3 = { module = "androidx.compose.material3:material3" }
compose-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
-androidx-activity-compose = "androidx.activity:activity-compose:1.9.1"
+androidx-activity-compose = "androidx.activity:activity-compose:1.9.3"
androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
[plugins]
# https://developer.android.com/build/releases/gradle-plugin
-android = { id = "com.android.application", version = "8.5.1" }
+# 8.7.0+ has false positive lint error for SCHEDULE_EXACT_ALARM permission https://issuetracker.google.com/issues/375352607
+android = { id = "com.android.application", version = "8.6.1" }
# https://kotlinlang.org/docs/releases.html#release-details
-# When updating, must also update kotlinCompilerExtensionVersion in app/build.gradle
-kotlin = { id = "org.jetbrains.kotlin.android", version = "1.9.24" }
+# When updating, must also update compose plugin.
+# Also look for compatible Gradle and Android Plugin versions:
+# https://kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin
+# Also coroutines version must be compatible.
+# 2.0.21 is compatible with Gradle 6.8.3–8.8 and AGP 7.1.3–8.5
+# Have to use 2.0 as the Room 2.6.1 annotation processor only supports metadata version up to 2.0,
+# but still using AGP 8.6 to compile with SDK 35 (as it appears there are only minor changes).
+kotlin = { id = "org.jetbrains.kotlin.android", version = "2.0.21" }
+compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version = "2.0.21" }
# https://github.com/ben-manes/gradle-versions-plugin/releases
versions = { id = "com.github.ben-manes.versions", version = "0.51.0" }
# https://github.com/gradle-nexus/publish-plugin/releases
diff --git a/store/README.md b/store/README.md
new file mode 100644
index 0000000000..66dd11ae26
--- /dev/null
+++ b/store/README.md
@@ -0,0 +1,5 @@
+# Text resources for stores
+
+Only edit the original file, [market_description.txt](market_description.txt).
+
+The other languages are [translated on and pulled from Crowdin](https://crowdin.com/project/seriesguide-translations).
diff --git a/store/ar/market_description.txt b/store/ar/market_description.txt
new file mode 100644
index 0000000000..c187791b06
--- /dev/null
+++ b/store/ar/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: مُدير المسلسل
+
+تابع مسلسلاتك وأفلامك المفضلة.
+
+مع تطبيق SeriesGuide يمكنك البحث عن المسلسلات والأفلام التي تشاهدها، وتتبع حلَقات المسلسلات والأفلام التي شاهدتها، والحصول على إشعارات حول الحلَقات القادمة وحِفظ نسخة احتياطية لها ومزامنتها عبر الأجهزة.
+
+اكتشف. ابحث عن المسلسلات ذات الحلَقات الجديدة المشهورة أو المشابهة للتي تشاهدها. استكشف الأفلام الموجودة حاليًا في السينما، التي تم إصدارها رقميًا أو على الأقراص.
+
+النسخ الاحتياطي والمزامنة. سجّل الدخول إلى سحابة SeriesGuide لإجراء النسخ الاحتياطي ومزامنة المسلسلات والقوائم والأفلام.
+
+يتعامل مع Trakt. سجّل الدخول إلى حساب Trakt الخاص بك للوصول إلى قائمة المشاهدة والمجموعة الخاصة بك، وخيار التحقق، والتقييم والتعليق. مزامنة الحلَقات والأفلام التي تم مشاهدتها بين التطبيقات ومركز الوسائط التي تدعمه Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+احصل على الاشتراك لفتح جميع الميزات ودعم SeriesGuide! ستحصل على المزيد من خيارات الويدجيت، إشعارات للحلقات الجديدة والمزيد. أنت أيضًا ستدعم استمرار الإصلاحات والميزات الجديدة.
+
+ملاحظة: لا يمكنك من مشاهدة حلقات المسلسلات أو الافلام. التاريخ الموضح هو تاريخ عرض الحلقات على المحطة الأصلية فقط.
+
+SeriesGuide X Unlock Key
+
+احصل على جميع مزايا SeriesGuide بشكل دائم. تحتاج إلى كل من SeriesGuide وهذا الـUnlock Key لكي تثبته.
+
+وصول كامل لجميع الترجمات
+فتح جميع المميزات، بما فيها التخزين الاحتياطي.
+
+فرع الداعمين
+دعم تطوير التحديثات في المستقبل. يشمل جميع الوصول.
+
+فرع للراعي
+مساهمة كبيرة لضمان التحديثات في المستقبل. يشمل جميع الوصول.
diff --git a/store/bg/market_description.txt b/store/bg/market_description.txt
new file mode 100644
index 0000000000..e6c717941a
--- /dev/null
+++ b/store/bg/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: Вашите сериали
+
+Следете Вашите любими сериали и филми.
+
+Със SeriesGuide можете да откривате сериали и филми за гледане, да следите кои епизоди и филми сте гледали, да получавате известия за предстоящи епизоди, както и да синхронизирате приложението между различните си устройства.
+
+Открийте. Можете да търсите сериали с нови епизоди, кои сериали са популярни или са подобни на тези, които гледате в момента. Можете да разгледате кои филми се излъчват по кината в момента, или са налични в стрийминг платформите или на диск.
+
+Бекъп и синхронизация. Влезте в SeriesGuide Cloud за да синхронизирате своите сериали, списъци и филми.
+
+Интеграция с Trakt. Свържете приложението със своя Trakt акаунт за достъп до вашия списък със сериали, колекцията си, да отбелязвате какво гледате или да оценявате и коментирате. Можете да синхронизирате гледаните епизоди и филми между различни приложения, поддържащи Trakt.
+
+Разширения. Можете да добавите бутон към външно приложение или да създадете свое собствено.
+
+Абонирайте се, за да получите достъп до всички функции и поддръжка за SeriesGuide! Ще получите допълнителни екстри в списъците, нотификациите за нови епизоди и други. Също така е включена поддръжка (корекции, поправки, ъпдейти и нови опции).
+
+ЗАБЕЛЕЖКА: Не позволява гледане на епизоди и филми. Датата на излъчване на епизодите е за излъчването по оригиналния канал.
+
+Ключ за SeriesGuide X
+
+Вземи постоянен достъп до X функциите на SeriesGuide. Изисква инсталация на SeriesGuide и ключа за активация.
+
+Пълен достъп
+Достъп до всички функции, включително бекъп в облака.
+
+Поддръжник
+Подкрепете ни, за да развиваме приложението. Включва Пълен достъп.
+
+Спонсор
+Подкрепете ни още повече, за да развиваме приложението. Включва Пълен достъп.
diff --git a/store/ca/market_description.txt b/store/ca/market_description.txt
new file mode 100644
index 0000000000..8d3854e9d0
--- /dev/null
+++ b/store/ca/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: gestor de sèries
+
+Fes un seguiment de les teves sèries de TV i pel·lícules favorites.
+
+Amb SeriesGuide podeu trobar programes i pel·lícules per veure, fer un seguiment dels vostres episodis i pel·lícules vistos, rebre notificacions sobre episodis propers i fer-ne una còpia de seguretat i sincronitzar-la entre dispositius.
+
+ Descobrir. Busqueu programes amb episodis nous, que són populars o similars als que esteu veient. Busqueu pel•lícules actualment en cinemes, publicades digitalment o en disc
+
+ Còpia de seguretat i sincronització. Inicieu la sessió a SeriesGuide Cloud per fer còpies de seguretat i sincronitzar els vostres programes, llistes i pel·lícules.
+
+ S'integra amb Trakt. Connecta el teu compte de Trakt per accedir a la vostra llista i col·lecció, registre, valoració i comentari. Sincronitza episodis i pel·lícules vistes entre aplicacions i centres multimèdia compatibles amb Trakt.
+
+Extensions. Afegeix un botó d'extensió proporcionat per una aplicació de tercers o construeix-ne un de propi.
+
+Obteniu la subscripció per desbloquejar totes les funcions i doneu suport a SeriesGuide! Obtindreu més opcions de ginys, notificacions per a episodis nous i molt més. També doneu suport a contínues correccions i noves característiques.
+
+NOTA: no podeu veure episodis ni pel·lícules. Als episodis només es mostra la data de llançament a la xarxa original.
+
+Clau de SeriesGuide X
+
+Desbloqueja permanentment l'accés a totes les funcionalitats de SeriesGuide. Requereix tenir instal·lat tant SeriesGuide com X Pass.
+
+Subscripció Accés Total
+Desbloquegeu totes les funcions, inclosa la còpia de seguretat de Cloud.
+
+Subscripció Supporter
+Suport al desenvolupament de futures actualitzacions Inclou Accés Total
+
+Subscripció Sponsor
+Gran contribució per garantir futures actualitzacions. Inclou Accés Total
diff --git a/store/ceb/market_description.txt b/store/ceb/market_description.txt
new file mode 100644
index 0000000000..835a40a4f9
--- /dev/null
+++ b/store/ceb/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Pagsubay sa imong mga paborito nga TV shows ug mga salida.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. Ni suporta usab ka sa mga gepadayun nga mga pag ayu ug mga bag.o nga mga bahin.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+E-unlock ang tanang pemanente nga pag-gamit sa tanang bahin sa SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/crowdin.yml b/store/crowdin.yml
new file mode 100644
index 0000000000..6b673dea52
--- /dev/null
+++ b/store/crowdin.yml
@@ -0,0 +1,16 @@
+# https://developer.crowdin.com/configuration-file/
+
+# Credentials (project_id, api_token, base_path, base_url) are stored in ~/.crowdin.yml
+
+# Drop folder hierarchy when uploading
+"preserve_hierarchy": false
+
+files: [
+ {
+ # Translation source file
+ "source": "/market_description.txt",
+
+ # Path of the translation file in the downloaded ZIP (see source file settings on Crowdin)
+ "translation": "/temp/%android_code%/%original_file_name%",
+ }
+]
\ No newline at end of file
diff --git a/store/cs/market_description.txt b/store/cs/market_description.txt
new file mode 100644
index 0000000000..1a0d4cffd4
--- /dev/null
+++ b/store/cs/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: Správce seriálů
+
+Mějte přehled ve svých oblíbených televizních seriálech a filmech.
+
+Se SeriesGuide můžete najít seriály a filmy ke sledování, uložte si přehled zhlédnutých epizod a filmů, dostávejte upozornění o nových epizodách a zálohujte je a synchronizujte je napříč zařízeními.
+
+Objevujte. Najděte seriály s novými epizodami, které jsou populární nebo podobné těm, které sledujete. Prozkoumejte filmy, které jsou aktuálně v kinech, vydané digitálně nebo na disku.
+
+Zálohujte a synchronizujte. Přihlaste se do SeriesGuide Cloudu pro zálohování a synchronizaci vašich seriálů, seznamů a filmů.
+
+Propojte s platformou Trakt. Propojením vašeho Trakt účtu získáte přístup k vašemu seznamu ke zhlédnutí a k vaší kolekci, k funkci zhlédnuto, k hodnocení a ke komentování. Synchronizujte zhlédnuté epizody a filmy mezi aplikacemi a mediálními centry podporovanými službou Trakt.
+
+Rozšíření. Přidejte tlačítko rozšíření, které poskytuje aplikace třetí strany nebo si vytvořte vlastní.
+
+Získejte předplatné pro odemknutí všech funkcí a podpořte SeriesGuide! Získáte více možností widgetu, notifikace o nových epizodách a další. Zároveň podpoříte pokračování oprav a nové funkce.
+
+POZNÁMKA: V aplikaci nemůžete sledovat epizody nebo filmy. U epizod je uvedeno pouze datum vydání v originální síti.
+
+Klíč k odemknutí SeriesGuide X
+
+Odemkněte trvalý přístup ke všem funkcím SeriesGuide. Vyžaduje nainstalovaný SeriesGuide i tento klíč.
+
+Předplatné s Veškerým přístupem
+Odemknou se vám všechny funkce, včetně Cloudového zálohování.
+
+Předplatné Podporovatel
+Podpoříte vývoj budoucích aktualizací. Zahrnuje veškerý přístup.
+
+Předplatné Sponzor
+Velký příspěvek k zajištění budoucích aktualizací. Zahrnuje veškerý přístup.
diff --git a/store/cy/market_description.txt b/store/cy/market_description.txt
new file mode 100644
index 0000000000..8ad05a77d0
--- /dev/null
+++ b/store/cy/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: rheolwr rhaglen
+
+Cadwch olwg ar eich hoff sioeau teledu a ffilmiau.
+
+Gyda SeriesGuide gallwch ddod o hyd i sioeau a ffilmiau i'w gwylio, cadw golwg ar eich penodau a'ch ffilmiau a wyliwyd, cael hysbysiadau am benodau sydd ar ddod a'i ategu a'i gysoni ar draws dyfeisiau.
+
+Darganfod. Dewch o hyd i sioeau gyda phenodau newydd, sy'n boblogaidd neu'n debyg i'r rhai rydych chi'n eu gwylio. Archwiliwch ffilmiau mewn sinemâu ar hyn o bryd, wedi'u rhyddhau'n ddigidol neu ar ddisg.
+
+Gwneud copi wrth gefn a sync. Mewngofnodi i SeriesGuide Cloud i wneud copi wrth gefn a sync eich sioeau, rhestrau a ffilmiau
+
+Yn integreiddio â Trakt. Cysylltwch eich cyfrif Trakt i gael mynediad i'ch rhestr wylio a'ch casgliad, gwirio i mewn, graddio a rhoi sylwadau. Gwyliodd Sync benodau a ffilmiau rhwng apiau a chanolfannau cyfryngau gyda chefnogaeth Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Sicrhewch y tanysgrifiad i ddatgloi pob nodwedd a chefnogi SeriesGuide! Byddwch yn cael mwy o opsiynau teclyn rhestr, hysbysiadau ar gyfer penodau newydd a mwy. Rydych hefyd yn cefnogi atebion parhaus a nodweddion newydd.
+
+SYLWCH: Ni allwch wylio penodau neu ffilmiau. Ar gyfer penodau dim ond y dyddiad rhyddhau ar y rhwydwaith gwreiddiol a restrir.
+
+SeriesGuide X Datgloi Allwedd
+
+Datgloi mynediad parhaol i holl nodweddion SeriesGuide. Mae angen gosod SeriesGuide a'r allwedd hon.
+
+Pob Is-fynediad
+Datgloi pob nodwedd, gan gynnwys Cloud wrth gefn.
+
+Is-gefnogwr
+Cefnogi datblygiad diweddariadau yn y dyfodol. Yn cynnwys Pob Mynediad.
+
+Noddwr Is
+Cyfraniad mawr i sicrhau diweddariadau yn y dyfodol. Yn cynnwys Pob Mynediad.
diff --git a/store/da/market_description.txt b/store/da/market_description.txt
new file mode 100644
index 0000000000..ca6546ac41
--- /dev/null
+++ b/store/da/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: Serieadmin.
+
+Hold styr på dine foretrukne TV-serier og film.
+
+Med SeriesGuide kan du finde TV-serier og film, du vil se, holde styr på dine sete episoder og film, modtage meddelelser om kommende episoder og sikkerhedskopiere og synkronisere lister på tværs af alle dine enheder.
+
+Gå på opdagelse. Find TV-serier med nye episoder, som er populære, eller svarer til de TV-serier, du ser i øjeblikket. Udforsk film, der vises i biograferne i øjeblikket, eller som er udgivet digitalt eller på disk.
+
+Sikkerhedskopiering og synkronisering. Log ind på SeriesGuide Cloud for at sikkerhedskopiere dine TV-serier, lister og film.
+
+Trakt-integration. Forbind din Trakt-konto for at tilgå din overvågningsliste og samling, checke ind, bedømme og kommentere. Synkronisér sete episoder og film mellem apps og mediecentre understøttet af Trakt.
+
+Udvidelser. Tilføj en udvidelsesknap stillet til rådighed af en tredjeparts-app, eller design din egen.
+
+Tegn et abonnement for at oplåse alle funktioner og støtte SeriesGuide! Du vil flere widget-funktioner, meddelelser om nye episoder og meget mere. Du støtter også kontinuerlige fejlrettelser og nye funktioner.
+
+BEMÆRK: Du kan ikke se episoder eller film. For episoder vises kun udgivelsesdatoen fra det oprindelige netværk.
+
+SeriesGuide X-oplåsningsnøgle
+
+Oplås alle funktioner i SeriesGuide permanent. Kræver, at både SeriesGuide og denne nøgle er installeret.
+
+All Access Sub
+Oplås alle funktioner, inklusive Cloud-backup.
+
+Supporter Sub
+Støt udviklingen af fremtidige opdateringer. Inkluderer All Access.
+
+Sponsor Sub
+Den store støtte-pakke, der sikrer fremtidige opdateringer. Inkluderer All Access.
diff --git a/store/de/market_description.txt b/store/de/market_description.txt
new file mode 100644
index 0000000000..65b06721db
--- /dev/null
+++ b/store/de/market_description.txt
@@ -0,0 +1,30 @@
+Behalte den Überblick über deine Lieblings-Serien und Filme.
+
+SeriesGuide ist eine kostenlose App um Serien und Filme zu entdecken, deine gesehenen Folgen und Filme im Überblick zu behalten, und über kommende Folgen benachrichtigt zu werden.
+
+Entdecke Serien und Filme : Finde heraus was auf deinen Streaming-Diensten verfügbar, neu, beliebt oder ähnlich zu dem ist, was du gerade schaust.
+
+Hilft dabei, was und wo man schauen kann : zeigt was als Nächstes zu schauen ist, und hilft herauszufinden, wo eine Serie oder ein Film gestreamt oder gekauft werden kann.
+
+Abhaken und den Überblick behalten : Führe eine Liste der gesehenen Folgen und Filme, sieh deinen Fortschritt. Bleibe über neue Folgen informiert.
+
+Sichern und synchronisieren : Benutze SeriesGuide Cloud um auf deine Serien, Listen und Filme auf mehreren Geräten zuzugreifen.
+
+Mit Trakt verbinden : Synchronisiere deine gesehenen und gesammelten Folgen und Filme, greife auf deinen Verlauf zu und sieh, was andere schauen. Gib Bewertungen ab und poste Kommentare.
+
+Kein Konto erforderlich : Wenn du möchtest, kannst du deine Bibliothek nur über JSON-Dateien sichern und wiederherstellen.
+
+Open Source und unabhängig : SeriesGuide wird von seinen Abonnenten unterstützt, nicht von Werbetreibenden oder Investoren.
+
+Für Hilfe, Fragen oder weitere Informationen besuche https://www.seriesgui.de/help
+
+Um den Quellcode anzusehen, besuche https://github.com/UweTrottmann/SeriesGuide
+
+All Access Abo
+Zugriff auf alle Funktionen, inklusive Cloud-Sicherung.
+
+Unterstützer Abo
+Unterstütze die Entwicklung zukünftiger Updates. Enthält All Access.
+
+Sponsor Abo
+Großer Beitrag um zukünftige Updates sicherzustellen. Enthält All Access.
diff --git a/store/download-translations.ps1 b/store/download-translations.ps1
new file mode 100644
index 0000000000..8388b13bd9
--- /dev/null
+++ b/store/download-translations.ps1
@@ -0,0 +1,16 @@
+# https://crowdin.github.io/crowdin-cli/
+crowdin.bat pull
+
+$downloadDir = ".\temp\"
+$destinationDir = "."
+
+Write-Host "Dropping region specifiers for all but [zh], [pt]..."
+Get-ChildItem -Path $downloadDir | Where-Object {$_.PsIsContainer -and $_.Name -notlike "*zh*" -and $_.Name -notlike "*pt*" } | Rename-Item -NewName { $_.Name -creplace "-r[A-Z]+", "" }
+
+Write-Host "Copying files..."
+Get-ChildItem -Path $downloadDir | Where-Object {$_.PsIsContainer} | Copy-Item -Destination $destinationDir -Force -Recurse
+
+Write-Host "Removing folder..."
+Remove-Item -Path $downloadDir -Recurse
+
+Write-Host "DONE"
diff --git a/store/el/market_description.txt b/store/el/market_description.txt
new file mode 100644
index 0000000000..2fb987712d
--- /dev/null
+++ b/store/el/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: διαχείριση σειρών
+
+Ακολουθήστε τις αγαπημένες σας τηλεοπτικές σειρές και ταινίες.
+
+Με το SeriesGuide μπορείτε να βρείτε σειρές και ταινίες για να δείτε, να παρακολουθείτε ποια επεισόδια και ταινίες έχετε δει, να λαμβάνετε ειδοποιήσεις για επερχόμενα επεισόδια, καθώς και να κάνετε λήψη αντιγράφων ασφαλείας και συγχρονισμό σε πολλαπλές συσκευές.
+
+Εξερεύνηση. Βρείτε σειρές με νέα επεισόδια, που είναι δημοφιλείς ή παρόμοιες με αυτές που παρακολουθείτε. Εξερευνήστε τις ταινίες που παίζονται στους κινηματογράφους, που κυκλοφορούν ψηφιακά ή σε δίσκους.
+
+Αντίγραφα ασφαλείας και συγχρονισμός. Συνδεθείτε στο SeriesGuide Cloud για λήψη αντιγράφων ασφαλείας και συγχρονισμό των σειρών, των λιστών και των ταινιών σας.
+
+Ενσωμάτωση με το Trakt. Συνδέστε το λογαριασμό Trakt σας για πρόσβαση στη λίστα παρακολούθησης και στις συλλογές σας, ενημέρωση κατάστασης, αξιολογήσεις και σχόλια. Συγχρονίστε τα επεισόδια και τις ταινίες που έχετε δει μεταξύ εφαρμογών και κέντρων πολυμέσων που υποστηρίζονται από το Trakt.
+
+Επεκτάσεις. Προσθέστε ένα κουμπί επέκτασης από μια τρίτη εφαρμογή ή αναπτύξτε τη δική σας.
+
+Αγοράστε μια συνδρομή για να ξεκλειδώσετε όλες τις δυνατότητες και να υποστηρίξετε το SeriesGuide! Θα αποκτήσετε περισσότερες επιλογές για widget, ειδοποιήσεις για νέα επεισόδια και άλλα. Υποστηρίζετε, επιπλέον, συνεχείς διορθώσεις και νέες δυνατότητες.
+
+ΣΗΜΕΙΩΣΗ: Δεν μπορείτε να δείτε επεισόδια ή ταινίες. Για τα επεισόδια, εμφανίζεται μόνο η ημερομηνία κυκλοφορίας από το αρχικό δίκτυο.
+
+Κλειδί SeriesGuide X
+
+Ξεκλειδώσετε μόνιμη πρόσβαση σε όλες τις δυνατότητες του SeriesGuide. Απαιτείται η εγκατάσταση τόσο του SeriesGuide όσο και αυτού του κλειδιού.
+
+Συνδρομή πλήρους πρόσβασης
+Ξεκλειδώστε όλες τις δυνατότητες, μαζί με τα αντίγραφα ασφαλείας στο Cloud.
+
+Υποστηρικτική συνδρομή
+Υποστηρίξτε την ανάπτυξη μελλοντικών ενημερώσεων. Περιλαμβάνει τη συνδρομή πλήρους πρόσβασης.
+
+Συνδρομή χορηγού
+Μεγάλη συνεισφορά για διαβεβαίωση μελλοντικών ενημερώσεων. Περιλαμβάνει τη συνδρομή πλήρους πρόσβασης.
diff --git a/market_description.txt b/store/eo/market_description.txt
similarity index 100%
rename from market_description.txt
rename to store/eo/market_description.txt
diff --git a/store/es/market_description.txt b/store/es/market_description.txt
new file mode 100644
index 0000000000..0b87eb6285
--- /dev/null
+++ b/store/es/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: Gestor de Series
+
+Mantén seguimiento de tus series y películas favoritas.
+
+Con SeriesGuide puedes encontrar series y películas para ver, seguir tus episodios y películas vistas, obtener notificaciones sobre los próximos episodios y hacer una copia de seguridad y sincronizarla entre dispositivos.
+
+Descubre. Encuentra los episodios de tus series favoritas o similares a las que estás viendo. Explora películas actualmente en cines, publicadas digitalmente o en disco.
+
+Copia de seguridad y sincronización. Inicie sesión en SeriesGuide para sincronizar sus series, listas, y películas desde la nube.
+
+Integrado con Trakt. Inicie sesión en su cuenta de Trakt para acceder a su lista de reproducción de modo que pueda registrar cualquier serie, evaluarla y comentarla. Sincronice los episodios y películas que ha visto en aplicaciones y centros multimedia soportados por Trakt.
+
+Extensiones. Añada una extensión proporcionada por una aplicación de terceros o cree la suya.
+
+¡Obtenga la suscripción para desbloquear todas las funciones y apoye a SeriesGuide! Tendrá más características disponibles, notificaciones de nuevos episodios y mucho más. También ayudas a que sigamos haciendo correcciones y añadiendo nuevas características.
+
+NOTA: Debe saber que esta aplicación no puede ver episodios o películas. Los episodios solo mostrarán la fecha en la que ha sido emitida.
+
+Clave de SeriesGuide X
+
+Desbloquea el acceso permanente a todas las funciones de SeriesGuide. Requiere tanto SeriesGuide como esta clave para instalarse.
+
+Acceda a todo
+Desbloquee todas las funciones, incluyendo la copia de seguridad en Cloud.
+
+Soporte para suscriptores
+Apoye el desarrollo de futuras actualizaciones. Incluye todo el acceso.
+
+Patrocinador
+Una gran contribución para asegurar futuras actualizaciones. Incluye todo el acceso.
diff --git a/store/fa/market_description.txt b/store/fa/market_description.txt
new file mode 100644
index 0000000000..7dc8040f08
--- /dev/null
+++ b/store/fa/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: مدیریت نمایشها
+
+حساب نمایشهای تلویزیونی و فیلمهای مورد علاقهتان را داشته باشید.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. و همچنین از رفع مشکلهای ادامهدار و ویژگیهای جدید حمایت خواهید کرد.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+کلید قفلگشایی SeriesGuide X
+
+باز کردن دسترسی دائمی به تمامی ویژگی های سریزگاید. نیاز به نصب SeriesGuide و این کلید دارد.
+
+اشتراک دسترسی کامل
+باز کردن تمام ویژگیها، شامل نسخه پشتیبان ابری.
+
+Supporter Sub
+Support development of future updates. شامل همهٔ دسترسیها.
+
+Sponsor Sub
+مشارکتی بزرگ برای اطمینان از بهروز رسانیهای آینده. شامل همهٔ دسترسیها.
diff --git a/store/fi/market_description.txt b/store/fi/market_description.txt
new file mode 100644
index 0000000000..bdefb265e6
--- /dev/null
+++ b/store/fi/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: Sarjojen ja elokuvien hallinta
+
+Pidä kirjaa suosikki ohjelmiesi ja elokuviesi katsomisesta.
+
+SeriesGuiden avulla voit löytää ohjelmia ja elokuvia katsottavaksi, pitää kirjaa katsomistasi jaksoista ja elokuvista, saada ilmoituksia tulevista jaksoista ja varmuuskopioida ja synkronoida tiedot laitteiden välillä.
+
+Löydä uutta katsottavaa. Löydä ohjelmia uusilla jaksoilla, jotka ovat suosittuja tai samanlaisia kuin katsomasi. Tutustu tällä hetkellä teattereissa pyöriviin, tai digitaalisesti tai levyllä julkaistuihin elokuviin.
+
+Varmuuskopiointi ja synkronointi. Kirjaudu sisään SeriesGuide Cloudiin varmuuskopioidaksesi ja synkronoidaksesi ohjelmasi, listasi ja elokuvasi.
+
+Integroituu Traktin kanssa. Yhdistä Trakt-tilisi, jotta voit hallita seurantalistaasi ja kokoelmaasi, kuitata, arvostella ja kommentoida. Synkronoi katsotut jaksot ja elokuvat Traktin tukemien sovellusten ja mediakeskusten välillä.
+
+Laajennukset. Lisää kolmannen osapuolen sovelluksen tarjoama laajennuspainike tai luo omasi.
+
+Avaa kaikki ominaisuudet ja tue SeriesGuidea hankkimalla tilaus! Saat lisää vaihtoehtoja lista-widgetille, ilmoituksia uusista jaksoista ja paljon muuta. Tuet samalla korjauksia ja uusia ominaisuuksia.
+
+HUOMAUTUS: Et voi katsella jaksoja tai elokuvia. Jaksojen kohdalla näkyy vain alkuperäisen verkon julkaisupäivä.
+
+SeriesGuide X -avausavain
+
+Avaa pysyvä pääsy kaikkiin SeriesGuiden ominaisuuksiin. Edellyttää sekä SeriesGuiden että tämän avaimen asentamista.
+
+Täysi pääsy -tilaus
+Avaa kaikki ominaisuudet, mukaan lukien pilvivarmuuskopiointi.
+
+Tukija-tilaus
+Tue tulevien päivitysten kehittämistä. Sisältää Täysi pääsy -tilauksen.
+
+Sponsori-tilaus
+Suuri panos tulevien päivitysten varmistamiseksi. Sisältää Täysi pääsy -tilauksen.
diff --git a/store/fil/market_description.txt b/store/fil/market_description.txt
new file mode 100644
index 0000000000..74254165c2
--- /dev/null
+++ b/store/fil/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Subaybayan ang iyong mga paboritong palabas sa TV at pelikula.
+
+Sa SeriesGuide makakahanap ka ng mga palabas at pelikulang papanoorin, subaybayan ang iyong mga pinanood na yugto at pelikula, makatanggap ng mga pagpapaunawa tungkol sa mga paparating na yugto at i-back up ito at i-sync ito sa mga ibang device.
+
+Tuklasin. Maghanap ng mga palabas na may mga bagong yugto, na sikat o katulad ng mga pinapanood mo. Galugarin ang mga pelikulang kasalukuyang nasa mga sinehan, inilabas nang digital o sa disc.
+
+I-backup at i-sync. Pumasok na sa SeriesGuide Cloud upang i-backup at i-sync ang iyong mga palabas, pelikula, at listahan ng mga pinapanood mo.
+
+Nakasama sa Trakt. Ikonekta ang iyong akawnt sa Trakt upang ma-akses ang iyong listahan ng mga pinapanood at koleksyon, tsek-in, ini-rate at kinomento. I-sync ang mga napanood na yugto at pelikula sa pagitan ng mga aplikasyon at media center na suportado ng Trakt.
+
+Mga Extension. Magdagdag ng pindutan ng extension na ibinigay ng isang third-party na aplikasyon o bumuo ng sarili mong aplikasyon.
+
+Kunin ang suskripsyon upang i-unlock ang lahat ng tampok at suportahan ang SeriesGuide! Makakakuha ka ng higit pang mga pagpipilian sa widget ng listahan ng mga pinanood mo, mga pagpapaunawa para sa mga bagong yugto at higit pa. Sinusuportahan mo rin ang patuloy na pag-aayos at mga bagong tampok.
+
+TANDAAN: Hindi ka maaaring manood ng mga yugto o pelikula. Para sa mga yugto lamang ang petsa ng pagpapalabas sa orihinal na network ang nakalista.
+
+SeriesGuide X Unlock Key
+
+I-unlock ang permanenteng akses sa lahat ng tampok ng SeriesGuide. Nangangailangan ng parehong SeriesGuide at ang susi na ito upang ma-install.
+
+All Access Sub
+I-unlock ang lahat ng tampok, kabilang ang Cloud backup.
+
+Supporter Sub
+Suportahan ang pag-unlad ng mga bagong update. Kasama ang All Access.
+
+Sponsor Sub
+Malaking kontribusyon para masiguro ang mga bagong update. Kasama ang All Access.
diff --git a/store/fr/market_description.txt b/store/fr/market_description.txt
new file mode 100644
index 0000000000..645e18f003
--- /dev/null
+++ b/store/fr/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide : Gestion séries
+
+Gardez une trace de vos émissions TV et films préférés.
+
+Avec SeriesGuide, vous pouvez trouver des séries et des films à regarder, garder une trace des épisodes et films regardés, être notifié sur les prochains épisodes et sauvegarder et synchroniser votre historique sur tous vos appareils.
+
+Découvrez. Trouvez des séries avec de nouveaux épisodes, qui sont populaires ou similaires à celles que vous regardez. Explorez les films actuellement au cinéma, sortis numériquement ou en dvd.
+
+Sauvegarde et synchronisation. Connectez-vous à SeriesGuide Cloud pour sauvegarder et synchroniser vos séries, listes et films.
+
+S'intègre à Trakt. Connectez votre compte Trakt pour accéder à votre liste de suivi et à votre collecte, enregistrement, évaluation et commentaire. Synchroniser les épisodes et les films vus entre les applications et les centres multimédia pris en charge par Trakt.
+
+Extensions. Ajoutez des extensions fournies par une autre application ou développez la vôtre.
+
+Abonnez vous pour débloquer toutes les fonctionnalités et soutenir SeriesGuide ! Vous obtiendrez plus d'options de widget, de notifications pour les nouveaux épisodes et plus encore. Vous soutiendrez également le développement de correctifs et de nouvelles fonctionnalités.
+
+REMARQUE : Vous ne pouvez pas regarder des épisodes ou des films. Pour les épisodes, seule la date de sortie sur le réseau original est répertoriée.
+
+SeriesGuide X Clef
+
+Déverrouiller l'accès à toutes les fonctionnalités de SeriesGuide. Requiert l'installation à la fois de SeriesGuide et de cette clé.
+
+Abonnement Tous Accès
+Débloquer toutes les fonctions, incluant la sauvegarde Cloud.
+
+Abonnement Supporter
+Soutenir le développement des prochaines mises à jours. Inclut Tous les Accès.
+
+Commanditaire
+Grosse contribution pour assurer les mises à jours futures. Inclut Tous les Accès.
diff --git a/store/gl/market_description.txt b/store/gl/market_description.txt
new file mode 100644
index 0000000000..dab66eebd5
--- /dev/null
+++ b/store/gl/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Mantén o seguimento das túas series e películas favoritas.
+
+Con SeriesGuide podes atopar series e películas para ver, facer un seguimento dos teus episodios e películas vistos, recibir notificacións acerca de novos episodios e respaldar e sincronizalo entre dispositivos.
+
+Descobre. Atopar series con novos episodios, que son populares ou son similares aos que están a ver. Explore movies currently in cinemas, released digitally or on disc.
+
+Respaldo e sincronización. Inicie sesión en SeriesGuide Cloud para facer copias de seguridade e sincronizar as súas series, listas e películas.
+
+Intégrase con Trakt. Conecte a súa conta de Trakt para acceder á súa lista de visualización e colección, check in, puntuación e comentario. Sincronice episodios e películas vistas entre aplicacións e centros multimedia soportados por Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Obtén a subscrición para desbloquear todas as funcións e soporte de SeriesGuide! Obterá máis opcións no widget de lista, notificacións para novos episodios e máis. Tamén axudas a que sigamos a facer correccións e a engadir novas características.
+
+NOTA: Non podes ver episodios ou películas. Para os episodios só se mostra a data de emisión na cadea orixinal.
+
+SeriesGuide X Unlock Key
+
+Desbloquea o acceso permanente a todas as funcións de SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+Acceso a todo
+Desbloquea todas as características, incluíndo a copia de seguridade na nube.
+
+Soporte
+Soporte o desenvolvemento de futuras actualizacións. Inclúe todos os accesos.
+
+Patrocinador
+Gran contribución para asegurar futuras actualizacións. Inclúe todos os accesos.
diff --git a/store/hi/market_description.txt b/store/hi/market_description.txt
new file mode 100644
index 0000000000..9a8122b6a5
--- /dev/null
+++ b/store/hi/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Keep track of your favorite TV shows and movies.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. You also support continued fixes and new features.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+Unlock permanent access to all features of SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/hr/market_description.txt b/store/hr/market_description.txt
new file mode 100644
index 0000000000..11da9f41b9
--- /dev/null
+++ b/store/hr/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Pratite svoje omiljene TV serije i filmove.
+
+Sa SeriesGuide možete pronaći shows i filmove za gledanje, pratitit pogledane episodes i filmove, dobiti obavijesti o nadolazećim episodes te iste sigurnosno kopirati i sinkronizirati na svim uređajima.
+
+ Otkrij. Pronađi shows s novim episodes, koji su popularni ili su slični onima koje gledate. Explore movies currently in cinemas, released digitally or on disc.
+
+Sigurnosno kopiranje i sinkronizacija Prijavite se u SeriesGuide Cloud kako bi ste napravili sigurnosnu kopiju i sinkronziaciju vaših shows, lists i filmova.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. Također pomažete u stalnim popravcima i novim sadržajima.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+Otključajte stalni pristup svim značajkama SeriesGuide-a. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/hu/market_description.txt b/store/hu/market_description.txt
new file mode 100644
index 0000000000..51cf094597
--- /dev/null
+++ b/store/hu/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Kísérd figyelemmel a kedvenc TV-műsoraid és filmjeid.
+
+A SeriesGuide-al műsorokat és filmeket kereshetsz, amiket megnéznél, követheted, hogy mely epizódokat és filmeket láttad, értesítést kaphatsz a következő epizódokról és szinkronizálhatod az adatokat több eszköz között a felhő segítségével.
+
+Felfedezés. Találj műsorokat új epizódokkal, amik népszerűek vagy hasonlóak azokhoz, amiket nézel. Fedezd fel a jelenleg mozikban, digitálisan vagy lemezen megjelenő filmeket.
+
+Mentés és szinkronizálás Jelentkezz be a SeriesGuide Cloud-ba, hogy menthesd és szinkronizálhasd műsoraid, listáid és filmjeid.
+
+Trakt integráció. Csatlakoztasd Trakt profilod, hogy hozzáférhess a kívánságlistádhoz és gyűjteményedhez, értékelj és hozzászólj. A megtekintett epizódok és filmek szinkronizálása a Trakt által támogatott alkalmazások és médiaközpontok között.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Szerezze be az előfizetést az összes funkció feloldásához és a SeriesGuide támogatásához! Több lista widget opciót, értesítéseket kap az új epizódokról és így tovább. Ezzel támogatod a további fejlesztést és javításokat.
+
+MEGJEGYZÉS: Sorozatokat és filmeket nem tudsz nézni. Az epizódoknál az eredeti csatornán való megjelenési dátum szerepel.
+
+SeriesGuide X Unlock Key
+
+Aktiváld az állandó hozzáférést a SeriesGuide összes funkciójához. Requires both SeriesGuide and this key to be installed.
+
+Teljes Hozzáférés Kategória
+Összes funkció feloldása, például a felhő mentés.
+
+Támogatói Kategória
+Támogasd a jövőbeni fejlesztéseket. Tartalmazza a Teljes Hozzáférést.
+
+Szponzor Kategória
+Nagy hozzájárulás a jövőbeli frissítésekhez. Tartalmazza a Teljes Hozzáférést.
diff --git a/store/in/market_description.txt b/store/in/market_description.txt
new file mode 100644
index 0000000000..a7f19dee01
--- /dev/null
+++ b/store/in/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Ikuti acara TV dan film favoritmu.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. Kau juga terus mendukung perbaikan dan fitur baru.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+Buka secara permanent akses ke semua fitur SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/it/market_description.txt b/store/it/market_description.txt
new file mode 100644
index 0000000000..a84521c23a
--- /dev/null
+++ b/store/it/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: gestione serie TV
+
+Tieni traccia delle tue serie TV e dei tuoi film preferiti.
+
+Con SeriesGuide, puoi trovare serie TV e film da guardare, tenere traccia dei tuoi episodi e film preferiti, ricevere notifiche sui prossimi episodi, salvarli e sincronizzarli su tutti i dispositivi.
+
+Esplora. Trova nuove serie TV, le più popolari o simili a quelle che stai guardando. Esplora i film attualmente al cinema, distribuiti in digitale o su disco.
+
+Backup e sincronizzazione. Accedi a SeriesGuide Cloud per fare un backup e sincronizzare le tue serie TV, liste e film.
+
+Integrato con Trakt. Collega il tuo account Trakt per accedere alla tua lista e aggiungere, valutare e commentare. Sincronizza gli episodi visti e i film tra le app e i mediacenter supportati da Trakt.
+
+Estensioni. Aggiungi un pulsante fornito da un'app di terze parti o creane uno tuo.
+
+Ottieni l'abbonamento per sbloccare tutte le funzioni e supportare SeriesGuide! Avrai più opzioni per i widget delle liste, notifiche per nuovi episodi e molto altro. Sosterrai lo sviluppo di continue correzioni e l'aggiunta di nuove funzionalità.
+
+NOTA BENE: Con questa app non puoi guardare serie TV o film. Solo per le serie TV la data di messa in onda indicata è della rete originaria.
+
+Chiave di sblocco SeriesGuide X
+
+Sblocca l'accesso permanente a tutte le funzionalità di SeriesGuide. Richiede l'installazione di SeriesGuide e di questa chiave.
+
+Tutti i Sotto accessi
+Sblocca tutte le funzioni, incluso il backup Cloud.
+
+Sotto supporto
+Supporta lo sviluppo di futuri aggiornamenti. Include tutti gli accessi.
+
+Sotto sponsor
+Un grande contributo per garantire aggiornamenti futuri. Include tutti gli accessi.
diff --git a/store/iw/market_description.txt b/store/iw/market_description.txt
new file mode 100644
index 0000000000..c7fa5b7200
--- /dev/null
+++ b/store/iw/market_description.txt
@@ -0,0 +1,34 @@
+SeriesGuide: show manager
+
+עקוב אחר תוכניות הטלוויזיה והסרטים האהובים עליך.
+
+עם SeriesGuide, את/ה יכול/ה לחפש לראות תוכניות וסרטים, לעקוב אחרי פרקים וסרטים שראית, לקבל עדכונים על פרקים חדשים שאמורים לצאת, לגבות ולסנכרן בין מכשירים.
+
+מצא. מצא סדרות ופרקים חדשים, פופולריים ודומים לסדרות שאתה צופה בהן. Explore movies currently in cinemas, released digitally or on disc.
+
+גיבוי. הכנס ל-SeriesGuide Cloud כדי לגבות ולסנכרן את רשימות הסדרות והסרטים שלך.
+
+התחבר עם Trakt
+
+התחבר עם חשבון ה Trakt שלך כדי לקבל גישה לרשימת הצפייה והאוסף שלך, לעשות צ'ק אין, לדרג ולהגיב. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+עשו מנוי על מנת לקבל גישה לכל האפשרויות ולתת תמיכה בSeriesGuide המנוי נותן עוד אופציות לווידג'טים, התראות לפרקים חדשים ועוד.
+
+ המינוי שלך גם יעזור לתמוך בפיתוח עדכונים ותכונות חדשות.
+
+הערה: באפליקציה אין אפשרות לצפייה בסדרות או סרטים. לפרקי סדרות רק תאריך השחרור של הרשת המקורית רשום.
+
+SeriesGuide X Unlock Key
+
+קבל גישה מלאה לכל התכונות של SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+מנוי גישה מלאה
+פתח את כל האפשרויות, כולל גיבוי ענן.
+
+תומכי כתוביות
+תמוך בפיתוחים בשביל עדכונים נוספים. כולל את גישה מלאה.
+
+ספונסרים
+תמיכה כספית גדולה תבטיח עדכונים נוספים. כולל את גישה מלאה.
diff --git a/store/ja/market_description.txt b/store/ja/market_description.txt
new file mode 100644
index 0000000000..e3de1b9649
--- /dev/null
+++ b/store/ja/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+お気に入りのテレビ番組や映画を追跡します。
+
+SeriesGuide を使用すると、視聴する番組や映画を見つけたり、視聴したエピソードや映画を記録したり、今後のエピソードに関する通知を取得したり、バックアップやデバイス間で同期したりできます。
+
+発見。 人気のあるエピソードや視聴中のエピソードに似ている、新しいエピソードの番組を検索します。 Explore movies currently in cinemas, released digitally or on disc.
+
+バックアップと同期。 SeriesGuide Cloud にサインインして、番組、リスト、映画をバックアップおよび同期します。
+
+Traktとの統合。 Trakt アカウントに接続して、ウォッチリストとコレクションにアクセスし、チェックイン、評価、コメントします。 アプリと Trakt がサポートするメディアセンターの間で、視聴したエピソードと映画を同期します。
+
+拡張機能。 サードパーティのアプリが提供する拡張機能ボタンを追加したり、独自に作成できます.
+
+サブスクリプションを取得して、すべての機能のロックを解除し、SeriesGuide をサポートしてください! リストウィジェットのオプション、新しいエピソードの通知などが追加されます。 また、継続的な修正と新機能もサポートします。
+
+注: エピソードや映画を見ることはできません。 エピソードは、元のネットワークのリリース日のみ表示されます。
+
+SeriesGuide X Unlock Key
+
+SeriesGuide のすべての機能に対して、永久的にアクセスのロックを解除しましょう。 Requires both SeriesGuide and this key to be installed.
+
+全アクセスサブ
+クラウドバックアップを含む、すべての機能をロック解除
+
+サポーターサブ
+機能アップデートの開発をサポート。 すべてのアクセスを含みます。
+
+スポンサーサブ
+将来のアップデートを確実にするために大きく貢献します。 すべてのアクセスを含みます。
diff --git a/store/ko/market_description.txt b/store/ko/market_description.txt
new file mode 100644
index 0000000000..1c9c6c19f9
--- /dev/null
+++ b/store/ko/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+즐겨보는 TV 프로그램과 영화를 찾아보세요.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. 또한 개발자가 지속적으로 개발을 할 수 있도록 지원할 수 있습니다.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+SeriesGuide의 모든 기능을 영구적으로 사용하세요. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/lt/market_description.txt b/store/lt/market_description.txt
new file mode 100644
index 0000000000..9a8122b6a5
--- /dev/null
+++ b/store/lt/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Keep track of your favorite TV shows and movies.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. You also support continued fixes and new features.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+Unlock permanent access to all features of SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/lv/market_description.txt b/store/lv/market_description.txt
new file mode 100644
index 0000000000..15f4b16c57
--- /dev/null
+++ b/store/lv/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Keep track of your favorite TV shows and movies.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. Tu arī atbalsti turpmākus labojumus un jaunas funkcijas.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+Unlock permanent access to all features of SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/market_description.txt b/store/market_description.txt
new file mode 100644
index 0000000000..c4283b0f62
--- /dev/null
+++ b/store/market_description.txt
@@ -0,0 +1,30 @@
+Keep track of your favorite TV shows and movies.
+
+SeriesGuide is a free app to discover TV shows and movies, keep track of your watched episodes and movies, and get notified about upcoming episodes.
+
+Discover TV shows and movies : find out what is available on your streaming services, new, popular or similar to what you are watching.
+
+Assists with what and where to watch : see what to watch next and find where a TV show or movie is available to stream or purchase.
+
+Check off and keep track : keep a record of watched episodes and movies, see your progress. Stay aware of new episodes.
+
+Back up and sync : access your TV shows, lists and movies on multiple devices using SeriesGuide Cloud.
+
+Connect to Trakt : sync your watched and collected episodes and movies, access your history and see what others are watching. Post ratings and comments.
+
+No account required : if you want, you can back up and restore your library using JSON files only.
+
+Open source and independent : SeriesGuide is supported by its subscribers, not by advertisers or investors.
+
+For help, questions or more information visit https://www.seriesgui.de/help
+
+To check out the source code, visit https://github.com/UweTrottmann/SeriesGuide
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/mk/market_description.txt b/store/mk/market_description.txt
new file mode 100644
index 0000000000..d759595d56
--- /dev/null
+++ b/store/mk/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Следете ги вашите омилени ТВ серии и филмови.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. Можете, исто така да добиете континуираната подршка за поправка на грешки и нови функции.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+Отклучен постојан пристап до сите функционалности на SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/nb/market_description.txt b/store/nb/market_description.txt
new file mode 100644
index 0000000000..fb70e79f2a
--- /dev/null
+++ b/store/nb/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Hold følge med dine favorittfilmer og -serier.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. Du støtter også fortsatte feilrettinger og nye funksjoner.
+
+Nb. Du kan ikke se episoder eller filmer. Lanseringsdato på originalkanalen er kun oppgitt for episoder.
+
+SeriesGuide X Unlock Key
+
+Lås opp permanent tilgang til alle funksjonene i SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/nl/market_description.txt b/store/nl/market_description.txt
new file mode 100644
index 0000000000..1da1f5caf8
--- /dev/null
+++ b/store/nl/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: series beheren
+
+Blijf op de hoogte van uw favoriete TV-series en films.
+
+Met SeriesGuide kunt u series en films vinden om te bekijken, bijhouden welke afleveringen en films u bekeken heeft, meldingen ontvangen over nieuwe afleveringen, een backup van uw gegevens makenen en deze tussen al uw apparaten synchroniseren.
+
+Discover. Ontdek populaire voor u aanbevolen series en nieuwe afleveringen. Ontdek films die momenteel in de bioscoop te zien zijn, digitaal of op schijf uitgebracht.
+
+Back-up en synchronisatie. Meld aan bij SeriesGuide Cloud om uw series, lijsten en films te back-uppen en te synchroniseren.
+
+Integratie met Trakt. Verbind met uw Trakt-account om toegang te hebben tot uw watchlist en verzameling, check-ins, beoordelingen en opmerkingen. Synchroniseer bekeken aflevering en films tussen apps en mediacentra die Trakt ondersteunen.
+
+Extensies. Voeg een extensie knop toe die verstrekt wordt door een derde partij of bouw er zelf een.
+
+Neem het abonnement om alle functies te ontgrendelen en SeriesGuide te steunen! U zal toegang krijgen tot meer lijstwidgets, meldingen voor nieuwe afleveringen en meer. U ondersteunt ook voortdurende verbeteringen en nieuwe mogelijkheden.
+
+OPMERKING: U kan geen afleveringen of films bekijken. Voor afleveringen wordt enkel de releasedatum op het oorspronkelijke netwerk getoond.
+
+SeriesGuide X ontgrendelingssleutel
+
+Krijg permanent toegang tot alle functies van SeriesGuide. Vereist dat zowel SeriesGuide als deze sleutel worden geïnstalleerd.
+
+All Access-abonnement
+Ontgrendel alle functies, inclusief back-up in de cloud.
+
+Supporter-abonnement
+Ondersteun de ontwikkeling van toekomstige updates. Inclusief All Access.
+
+Sponsor-abonnement
+Grote bijdrage om toekomstige updates te garanderen. Inclusief All Access.
diff --git a/store/pl/market_description.txt b/store/pl/market_description.txt
new file mode 100644
index 0000000000..3606bf254e
--- /dev/null
+++ b/store/pl/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: menedżer seriali
+
+Zarządzaj oglądaniem swoich ulubionych seriali i filmów.
+
+Dzięki SeriesGuide możesz wyszukiwać seriale i filmy do obejrzenia, śledzić obejrzane odcinki i filmy, otrzymywać powiadomienia o nadchodzących odcinkach, wykonywać kopię zapasową i synchronizować pomiędzy urządzeniami.
+
+Odkrywaj. Znajdź seriale, które są popularne lub podobne do tych, które oglądasz. Przeglądaj filmy aktualnie wyświetlane w kinach, wydanych cyfrowo lub na płytach.
+
+Kopia zapasowa i synchronizacja. Zaloguj się do SeriesGuide Cloud, aby wykonać kopię zapasową lub zsynchronizować seriale, listy i filmy.
+
+Integracja z Trakt. Połącz swoje konto Trakt, aby uzyskać dostęp do listy obejrzanych i kolekcji, współdzielonych, ocen i komentarzy. Synchronizuj obejrzane odcinki i filmy pomiędzy aplikacjami i centrami multimedialnymi obsługiwanymi przez Trakt.
+
+Rozszerzenia. Dodaj przycisk rozszerzenia z zewnętrznej aplikacji, albo zbuduj własne rozszerzenie.
+
+Kup subskrypcję, aby odblokować wszystkie funkcję i wesprzeć SeriesGuide! Otrzymasz więcej opcji widżetów list, powiadomień o nowych odcinkach i więcej. W ten sposób wspierasz też rozwój nowych funkcji i wprowadzanie poprawek.
+
+UWAGA: aplikacja nie umożliwia oglądania filmów, ani seriali. Wyświetlane są tylko daty pierwszej emisji na oryginalnej stacji.
+
+Klucz odblokowujący SeriesGuide X
+
+Odblokuj dostęp do wszystkich funkcji SeriesGuide. Wymaga instalacji zarówno SeriesGuide, jak i tego klucza.
+
+Subskrypcja "pełen dostęp"
+Odblokuj wszystkie funkcje, włącznie z kopią zapasową w chmurze.
+
+Subskrypcja wspierająca
+Wspiera rozwój przyszłych aktualizacji. Zawiera "pełen dostęp".
+
+Subskrypcja sponsorująca
+Duża dotacja zapewniająca przyszłe aktualizacje. Zawiera "pełen dostęp".
diff --git a/store/pt-rBR/market_description.txt b/store/pt-rBR/market_description.txt
new file mode 100644
index 0000000000..893cfaa2f2
--- /dev/null
+++ b/store/pt-rBR/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: gerenciador de séries
+
+Organizar de perto seus séries de TV e filmes favoritos.
+
+Com o SeriesGuide você pode encontrar séries e filmes para assistir, acompanhar episódios e filmes assistidos, ter notificações sobre os próximos episódios, fazer backup e sincronizar entre dispositivos.
+
+Descubra. Encontre séries com novos episódios, que são populares ou semelhantes àquelas que você está assistindo. Explore filmes que estão nos cinemas, lançados digitalmente ou em disco.
+
+Backup e sincronização. Entre no SeriesGuide Cloud para fazer backup e sincronizar suas séries, listas e filmes.
+
+Integrado com o Trakt. Conecte sua conta do Trakt para acessar sua lista e coleção, verificar, avaliar e comentar. Sincronizar episódios e filmes assistidos entre apps e media centers suportados pelo Trakt.
+
+Extensões. Adicione um botão de extensão fornecido por um aplicativo de terceiros ou faça o seu em.
+
+Obtenha a assinatura para desbloquear todos os recursos e apoiar o SeriesGuide! Você receberá mais opções de widget, notificações para novos episódios e muito mais. Você também estará dando apoio a correções de erros e novas funcionalidades.
+
+AVISO: O app não é feito para assistir séries ou filmes. Para episódios, apenas a data de lançamento na rede oficial é listada.
+
+Chave de desbloqueio do SeriesGuide X
+
+Desbloqueie acesso permanente a todas as funcionalidades do SeriesGuide. Requer a instalação do SeriesGuide e da chave.
+
+Assinatura de Acesso Total
+Desbloqueia todos os recursos, incluindo o backup na nuvem.
+
+Assinatura de Apoio
+Apoia o desenvolvimento de atualizações. Inclui o Acesso Total.
+
+Assinatura de Patrocinador
+Grande contribuição para garantir atualizações. Inclui o Acesso Total.
diff --git a/store/pt-rPT/market_description.txt b/store/pt-rPT/market_description.txt
new file mode 100644
index 0000000000..397666495e
--- /dev/null
+++ b/store/pt-rPT/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: organize séries
+
+Organiza e guarda um registo dos teus filmes e séries favoritos.
+
+Com o SeriesGuide podes encontrar que filmes e séries ver, estar a par sobre o que já viste, receber notificações de episódios que estejam prestes a estrear e de poderes guardar toda essa informação e de partilhá-la entre dispositivos.
+
+Explora. Encontra séries com novos episódios, que são populares ou semelhantes aos que estás a ver. Explora os filmes atualmente nos cinemas, lançados em formato físico ou digital.
+
+Cópia de segurança e sincronização. Inicia Sessão no SeriesGuide Cloud para fazer uma cópia de segurança e sincronizar as tuas séries, listas e filmes.
+
+Integração com o Trakt. Conecta a tua conta Trakt para acederes à tua lista e coleção, para classificar e comentar. Sincronizar episódios e filmes vistos entre aplicações e centros de multimédia suportados pelo Trakt.
+
+Extensões. Adiciona uma extensão fornecida por uma aplicação de terceiros ou cria a tua própria extensão.
+
+Obtém a assinatura para desbloquear todos os recursos e apoiar o SeriesGuide! Receberás mais opções de widget, notificações de novos episódios e muito mais. Ao adquirires também estarás a apoiar correções contínuas e novas funcionalidades.
+
+NOTA: Esta app não é para ver filmes ou séries!!! Apenas para organizar o que vês! Nas Séries apenas é indicado a data de estreia do canal original.
+
+SeriesGuide X Desbloquear Pass
+
+Desbloqueia o acesso permanente a todas as funcionalidades do SeriesGuide. Requer a instalação do SeriesGuide e deste Pass.
+
+Subscrição de Acesso Total
+Desbloqueia todos os recursos, incluindo a cópia de segurança na Cloud.
+
+Subscrição de Apoio
+Apoia o desenvolvimento de atualizações. Inclui o Acesso Total.
+
+Subscrição de Patrocinador
+Grande contribuição para garantir futuras atualizações. Inclui o Acesso Total.
diff --git a/store/ro/market_description.txt b/store/ro/market_description.txt
new file mode 100644
index 0000000000..689daff129
--- /dev/null
+++ b/store/ro/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Ține evidența serialelor și filmelor tale favorite.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. Ajuți la adăugarea de noi opțiuni și la rezolvarea problemelor.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+Deblochează permanent accesul la toate optiunile SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/ru/market_description.txt b/store/ru/market_description.txt
new file mode 100644
index 0000000000..a08fd4f526
--- /dev/null
+++ b/store/ru/market_description.txt
@@ -0,0 +1,31 @@
+SeriesGuide: менеджер сериалов
+
+Следите за вашими любимыми сериалами и фильмами.
+
+С SeriesGuide вы можете находить сериалы и фильмы для просмотра, отслеживать просмотренные серии и фильмы, получать уведомления о предстоящих сериях, создавать резервные копии и синхронизировать их между всеми устройствами.
+
+Исследуйте. Находите сериали с новыми сериями, которые популярны или похожи на те, которые вы смотрите. Изучайте фильмы, которые идут сейчас в кинотеатре, выпущены в цифровой форме или на носителях.
+
+Резервное копирование и синхронизация. Войдите в SeriesGuide Cloud для резервного копирования и синхронизации сериалов, списков и фильмов.
+
+Интеграция с Trakt. Подключите свою учётную запись Trakt для доступа к списку просмотров и колекции, зарегистрируйтесь, оцениваете и комментируйте. Синхронизируйте просмотренные серии и фильмы между программами и медиа центрами, которые поддерживаются Trakt.
+
+Расширения. Добавьте кнопку расширения, предоставленную сторонним приложением, или создайте свою собственную.
+
+Оформите подписку, чтобы разблокировать все функции и поддержать SeriesGuide!
+Вы получите больший перечень параметров виджетов, уведомления о новых сериях и многое другое. Также с помощью подписки вы поддерживаете дальнейшие исправления и появление новых функций.
+
+Примечание: Вы не можете смотреть эпизоды или фильмы. Для эпизодов указывается только дата выпуска оригинальной сети.
+
+SeriesGuide x ключ разблокировки
+
+Откройте постоянный доступ ко всем функциям SeriesGuide. Требуется установить как SeriesGuide, так и этот ключ.
+
+Полный доступ
+Разблокировать все функции, включая резервное облачное копирование.
+
+Поддержать проект
+Поддержать разработку будущих обновлений. Включает полный доступ.
+
+Спонсировать проект
+Большой вклад в обеспечение будущих обновлений. Включает полный доступ.
diff --git a/store/si/market_description.txt b/store/si/market_description.txt
new file mode 100644
index 0000000000..9a8122b6a5
--- /dev/null
+++ b/store/si/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Keep track of your favorite TV shows and movies.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. You also support continued fixes and new features.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+Unlock permanent access to all features of SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/sk/market_description.txt b/store/sk/market_description.txt
new file mode 100644
index 0000000000..c6252a0710
--- /dev/null
+++ b/store/sk/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: správca seriálov
+
+Majte prehľad o svojich obľúbených seriáloch a filmoch.
+
+S programom SeriesGuide môžete nájsť seriály a filmy, ktoré chcete pozerať, sledovať svoje videné epizódy a filmy, dostávať upozornenia na nadchádzajúce epizódy a zálohovať ich a synchronizovať medzi rôznymi zariadeniami.
+
+ Objaviť. Nájdite relácie s novými epizódami, ktoré sú populárne alebo podobné tým, ktoré sledujete. Preskúmajte filmy aktuálne v kinách, vydané digitálne alebo na disku.
+
+ Zálohujte a synchronizujte. Prihláste sa do služby SeriesGuide Cloud pre zálohu a synchronizáciu svojich seriálov, zoznamov a filmov.
+
+Integrácia Trakt. Pripojte svoj účet Trakt, aby ste mali prístup k chcem vidieť a zbierke, videné, hodnotiť a komentovať. Synchronizujte sledované epizódy a filmy medzi aplikáciami a mediálnymi centrami podporovanými službou Trakt.
+
+ Rozšírenia. Pridajte tlačidlo rozšírenia poskytované aplikáciou tretej strany alebo si vytvorte vlastné.
+
+Získajte predplatné pre odomknutie všetkých funkcií a podporu SeriesGuide! Získate viac možností widgetu, oznámenia o nových epizódach a ďalšie. Zároveň podporíte autora aby opravoval chyby a pridával nové funkcie.
+
+POZNÁMKA: Aplikácia neslúži na sledovanie seriálov alebo filmov. Pre epizódy je uvedený iba dátum vydania na pôvodnej sieti.
+
+SeriesGuide X Kľúč
+
+Odomknite stály prístup ku všetkým funkciám v SeriesGuide. Vyžaduje inštaláciu SeriesGuide a aj tohto kľúča.
+
+Predplatné "Úplný prístup"
+Odomkne všetky funkcie, vrátane zálohovania na Cloud.
+
+Podporné predplatné
+Podporiť vývoj budúcich aktualizácií. Zahŕňa "Úplný prístup".
+
+Sponzorské predplatné
+Veľký príspevok na zabezpečenie budúcich aktualizácií. Zahŕňa "Úplný prístup".
diff --git a/store/sl/market_description.txt b/store/sl/market_description.txt
new file mode 100644
index 0000000000..9a8122b6a5
--- /dev/null
+++ b/store/sl/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Keep track of your favorite TV shows and movies.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. You also support continued fixes and new features.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+Unlock permanent access to all features of SeriesGuide. Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/sr/market_description.txt b/store/sr/market_description.txt
new file mode 100644
index 0000000000..1119f769f2
--- /dev/null
+++ b/store/sr/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: менаџер емисије
+
+Пратите омиљене ТВ емисије и филмове.
+
+Са SeriesGuide можете да пронађете емисије и филмове за гледање, да пратите ваше гледане епизоде и филмове, да добијате обавештења о предстојећим епизодама и да инаправите резервну копију и синхронизујете на свим уређајима.
+
+Откријте. Пронађите серије са новим епизодама, које су популарне или сличне онима које гледате. Истражите филмове који су тренутно у биоскопима, објављени дигитално или на диску.
+
+Архивирање и синхронизација. Пријавите се на SeriesGuide Cloud да направите архиву и синхронизујете емисије, листе и филмове.
+
+Интеграција са Trakt. Повежите Trakt налог да бисте приступили вашој листи за гледање и колекцији, оцене и коментаре. Синхронизујте гледане епизоде и филмове између апликација и медијских центара који подржавају Trakt.
+
+Додатци. Додајте дугме за додатак које обезбеђује апликација треће стране или направите сопствену.
+
+Набавите претплату да бисте откључали све функције и подржили SeriesGuide! Добићете више опција виџета за листу, обавештења о новим епизодама и још много тога. Такође подржавате стално исправљање грешака и додавање нових опција.
+
+НАПОМЕНА: Не можете гледати епизоде или филмове. За епизоде је наведен само датум објављивања на оригиналној мрежи.
+
+SeriesGuide X Кључ откључавања
+
+Откључајте трајни приступ свим функцијама SeriesGuide-а. Потребно да буду инсталирани и SeriesGuide и овај кључ.
+
+Пун приступ
+Откључајте све функције, укључујући архивирање у облаку.
+
+Подршка
+Подржите развој будућих ажурирања. Укључује „Пун приступ“.
+
+Спонсор
+Велики допринос обезбеђивању будућих ажурирања. Укључује „Пун приступ“.
diff --git a/store/sv/market_description.txt b/store/sv/market_description.txt
new file mode 100644
index 0000000000..4354b3ac59
--- /dev/null
+++ b/store/sv/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: seriehanterare
+
+Håll koll på dina favoriter bland tv-serier och filmer.
+
+Med SeriesGuide kan du hitta serier och filmer att titta på, hålla koll på vilka avsnitt och filmer du sett, bli notifierad om uppkommande avsnitt och säkerhetskopiera och synka det mellan enheter.
+
+Upptäck. Hitta serier med nya avsnitt, som är populära eller liknar de du tittar på. Upptäck filmer som visas på bio, har släppts digitalt eller på skiva.
+
+Säkerhetskopiera och synka. Logga in på SeriesGuide Cloud för att säkerhetskopiera och synka dina serier, listor och filmer.
+
+Integrerar med Trakt. Koppla ditt Trakt-konto för att få tillgång till din bevakningslista och samling, checka in, betygsätt och kommentera. Synka sedda avsnitt och filmer mellan appar och mediacenter som stöds av Trakt.
+
+Tillägg. Lägg till en tilläggsknapp tillhandahållen av en tredjepartsapp eller bygg din egen.
+
+Prenumerera för att låsa upp alla funktioner och stödja SeriesGuide! Du får fler widgetalternativ, notifieringar om nya avsnitt och mera. Du ger även ditt stöd för fortsatta buggfixar och nya funktioner.
+
+NOTERA: Du kan inte titta på avsnitt eller filmer. För avsnitt är endast sändningsdatumet hos det ursprungliga Tv-nätverket listat.
+
+SeriesGuide X upplåsningsnyckel
+
+Lås upp permanent tillgång till alla funktioner i SeriesGuide. Kräver att både SeriesGuide och denna nyckel är installerade.
+
+Full tillgångs-prenumeration
+Lås upp alla funktioner, inklusive säkerhetskopiering till molnet.
+
+Anhängare-prenumeration
+Stöd utveckling av framtida uppdateringar. Inkluderar full tillgång.
+
+Sponsor-prenumeration
+Stort bidrag för att säkerställa framtida uppdateringar. Inkluderar full tillgång.
diff --git a/store/ta/market_description.txt b/store/ta/market_description.txt
new file mode 100644
index 0000000000..c9b7ad3455
--- /dev/null
+++ b/store/ta/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+உங்களுக்குத தொலைக்காட்சி நிகழ்ச்சிகள் மற்றும் திரைப்படம் கண்காணியுங்கள்.
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. நீங்கள் ஆதரவு தொடர்ந்து மெருகூட்டப்பட்ட மற்றும் புதிய அம்சங்கள்.
+
+NOTE: You can not watch episodes or movies. For episodes only the release date on the original network is listed.
+
+SeriesGuide X Unlock Key
+
+SeriesGuide எல்லா அம்சங்களை நிரந்தர அணுகலை பூட்டவிழ். Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+Unlock all features, including Cloud backup.
+
+Supporter Sub
+Support development of future updates. Includes All Access.
+
+Sponsor Sub
+Big contribution to ensure future updates. Includes All Access.
diff --git a/store/th/market_description.txt b/store/th/market_description.txt
new file mode 100644
index 0000000000..ab7174f527
--- /dev/null
+++ b/store/th/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: ตัวจัดการซีรีส์และภาพยนตร์
+
+ติดตามการรับชมซีรีส์และภาพยนตร์ที่คุณชื่นชอบ
+
+ด้วย SeriesGuide คุณสามารถค้นหาซีรีส์และภาพยนตร์ที่คุณชื่นชอบ, ติดตามประวัติการรับชมของคุณ, รับการแจ้งเตือนเมื่อมีตอนใหม่ และสำรองและซิงค์ข้อมูลบนทุกอุปกรณ์
+
+ค้นพบ ค้นหาซีรีส์พร้อมตอนใหม่, ที่กำลังได้รับความนิยม หรือใกล้เคียงกับซีรีส์ที่คุณกำลังรับชมอยู่ สำรวจภาพยนตร์ที่กำลังฉายในโรง, วางจำหน่ายแบบดิจิทัลหรือแผ่น
+
+สำรองและซิงค์ เข้าสู่ระบบ SeriesGuide Cloud เพื่อสำรองและซิงค์ข้อมูลซีรีส์, รายการ และภาพยนตร์ของคุณ
+
+ทำงานร่วมกับ Trakt เชื่อมต่อบัญชี Trakt ของคุณเพื่อเข้าถึงรายการและคอลเลคชั่น, เช็กอิน, การให้คะแนน และแสดงความคิดเห็น ซิงค์ซีรีส์และภาพยนตร์ที่ดูแล้วระหว่างแอปและศูนย์กลางสื่อที่รองรับโดย Trakt
+
+ส่วนเสริม เพิ่มปุ่มส่วนเสริมที่ให้บริการโดยแอปภายนอกหรือสร้างของคุณเอง.
+
+สมัครสมาชิกเพื่อปลดล็อกคุณสมบัติทั้งหมดและสนับสนุน SeriesGuide! คุณจะได้รับตัวเลือกเพิ่มเติมสำหรับวิดเจ็ตรายการ, การแจ้งเตือนเมื่อมีตอนใหม่ ฯลฯ คุณยังได้สนับสนุนการแก้ไขปรับปรุงและคุณสมบัติใหม่ในอนาคต
+
+หมายเหตุ: คุณไม่สามารถรับชมซีรีส์หรือภาพยนตร์ได้, วันที่เผยแพร่ตอนของซีรีส์จะแสดงเฉพาะการออกอากาศครั้งแรก
+
+กุญแจปลดล็อก SeriesGuide X
+
+ปลดล็อกการเข้าถึงคุณสมบัติทั้งหมดของ SeriesGuide อย่างถาวร จำเป็นต้องติดตั้งทั้งแอป SeriesGuide และแอปกุญแจ
+
+สมาชิกแบบทุกคุณสมบัติ
+ปลดล็อกคุณสมบัติทั้งหมด, รวมทั้งการสำรองข้อมูลบนคลาวด์
+
+สมาชิกแบบผู้สนับสนุน
+สนับสนุนการพัฒนาอัปเดตใหม่ในอนาคต รวมสมาชิกแบบทุกคุณสมบัติ
+
+สมาชิกแบบสปอนเซอร์
+มีส่วนร่วมอย่างมาก เพื่อให้แน่ใจว่ามีการอัปเดตในอนาคต รวมสมาชิกแบบทุกคุณสมบัติ
diff --git a/store/tr/market_description.txt b/store/tr/market_description.txt
new file mode 100644
index 0000000000..c68781be02
--- /dev/null
+++ b/store/tr/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+Favori dizi ve filmlerinizi takip edin.
+
+SeriesGuide ile, izlemek için dizi ve filmler bulabilirsin, izlediğin bölümlerin ve filmlerin takibini yapabilirsin, çıkacak olan bölümler hakkında bildirim alabilir ve yedekleyip cihazlar arasında senkronize edebilirsin.
+
+Keşfet Popüler olan ve ya izlediklerine benzer dizileri yeni bölümleriyle bulabilirsin. Şuanda sinemalarda olan, dijital olarak veya diskte yayınlanan filmleri keşfedin.
+
+Yedekle ve senkronize et Dizilerinizi, listelerinizi ve filmlerinizi yedekleyip senkronize etmek için SeriesGuide Bulut hizmetine giriş yapın.
+
+Trakt ile entegrasyon İzleme listene ve koleksiyonuna, izlemeye devam ettiklerine, oylarına ve yorumlarına erişim sağlamak için Trakt hesabınızı bağlayın. İzlenmiş bölümleri ve filmleri uygulamalar arasında ve Trakt tarafından desteklenen medya medya merkezleriyle senkronize edin.
+
+Eklentiler Üçüncü taraf bir uygulamadan sağlanan bir eklenti butonu ekleyin ve ya kendinizinkini yapın.
+
+Tüm özellikleri açmak ve SeriesGuide' i desteklemek için abonelik alın! Alacaklarınız; daha fazla liste widgeti seçeneği, yeni bölümler için bildirimler ve daha fazlası. Ayrıca hata düzeltmelerini ve yeni özelliklerin gelişini desteklersiniz.
+
+NOT: Dizi veya filmleri izleyemezsiniz. Bölümler için yalnızca orijinal ağdaki yayınlanma tarihi listelenir.
+
+SeriesGuide X Unlock Key
+
+SeriesGuide'ın tüm özellikleri için sınırsız erişimi açın. Requires both SeriesGuide and this key to be installed.
+
+Tam Erişim Üyeliği
+Bulut yedekleme dahil olmak üzere bütün özellikleri açın.
+
+Destekçi Üyeliği
+Gelecekteki güncellemelerin geliştirilmesini destekle. Tam Erişim Üyeliğini de içerir.
+
+Sponsor Üyeliği
+Gelecekteki güncellemelerin teminatı için büyük bir katkı. Tam Erişim Üyeliğini de içerir.
diff --git a/store/uk/market_description.txt b/store/uk/market_description.txt
new file mode 100644
index 0000000000..831d6deabe
--- /dev/null
+++ b/store/uk/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: серіали та кіно
+
+Впорядкуйте перегляд ваших улюблених серіалів та фільмів.
+
+Із SeriesGuide ви можете знаходити серіали та фільми, щоб дивитися, відстежувати переглянуті серії та фільми, отримувати сповіщення про майбутні серії і робити резервні копії цієї інформації на всіх пристроях.
+
+Досліджуйте. Знаходьте серіали з новими епізодами, які популярні або подібні до тих, які ви дивитесь. Досліджуйте фільми, що вийшли у прокат (у кінотеатрах, стрімінгових сервісах, на дисках).
+
+Резервне копіювання і синхронізація. Увійдіть у SeriesGuide Cloud для резервного копіювання і синхронізації серіалів, списків та фільмів.
+
+Інтеграція з Trakt. Підключіть свій обліковий запис Trakt для доступу до списку перегляду та колекції, зареєструйтеся, поставте рейтинг і залиште коментар. Синхронізуйте переглянуті серіали та фільми між програмами та медіа центрами, які підтримуються Trakt.
+
+Розширення. Додайте кнопку розширення, надану стороннім додатком, або створіть власну.
+
+Отримайте підписку на розблокування всіх функцій і підтримки SeriesGuide! Ви отримаєте більший перелік параметрів віджетів, сповіщення про нові серії та інше. Ви також підтримуєте продовження виправлень та нові функції.
+
+Увага: Ви не можете дивитись серіали або фільми. Для серій вказана лише дата релізу в оригінальній мережі.
+
+Ключ розблокування SeriesGuide X
+
+Розблокуйте всі можливості SeriesGuide. Для цього потрібні SeriesGuide та ключ розблокування.
+
+Підписка на всі підрозділи
+Розблокувати всі функції, включаючи резервну копію в хмарі.
+
+Підтримка постачальника
+Підтримати розробку майбутніх оновлень. Включає доступ до всього.
+
+Підтримка спонсора
+Великий внесок у забезпечення майбутніх оновлень. Включає доступ до всього.
diff --git a/store/zh-rCN/market_description.txt b/store/zh-rCN/market_description.txt
new file mode 100644
index 0000000000..b5cfdd1e42
--- /dev/null
+++ b/store/zh-rCN/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide:剧集管家
+
+追踪管理您喜爱的电视节目和电影。
+
+通过 SeriesGuide 您可以找到要观看的节目和电影,跟踪您已观看的剧集和电影,获取即将播出剧集的通知并在不同设备之间备份与同步。
+
+发现。 寻找正在热播或与你正在观看的节目相似的更新中剧集。 浏览最近上映、数字发行或光盘发行的电影
+
+备份与同步。 登录到 SeriesGuide Cloud 以备份并同步您的节目、列表和电影。
+
+ 与 Trakt 集成。 连接到您的 Trakt 帐户以访问您的待看清单和收藏,签到、评分与评论。 在 Trakt 支持的应用程序和媒体中心之间同步已观看的剧集和电影。
+
+扩展。 添加第三方应用程序提供的扩展按钮,或构建您自己的扩展。
+
+通过获取订阅来解锁所有功能并支持 SeriesGuide! 您将获得更多列表微件设置选项、剧集更新通知等等。 您的支持将帮助我们把 SeriesGuide 做得更好。
+
+注意:并不能观看影视剧集。 对于剧集,仅列出原始网络上的发布日期。
+
+SeriesGuide X 解锁密钥
+
+解锁 SeriesGuide 所有功能的永久使用权, 需要同时安装 SeriesGuide 和此密钥。
+
+全功能订阅
+解锁所有功能,包括云备份。
+
+支持者订阅
+支持未来更新的开发。 包括所有功能。
+
+赞助者订阅
+为确保未来更新进行大额赞助。 包括所有功能。
diff --git a/store/zh-rTW/market_description.txt b/store/zh-rTW/market_description.txt
new file mode 100644
index 0000000000..1088753a4a
--- /dev/null
+++ b/store/zh-rTW/market_description.txt
@@ -0,0 +1,30 @@
+SeriesGuide: show manager
+
+追踪你最喜歡的電視節目和電影。
+
+With SeriesGuide you can find shows and movies to watch, keep track of your watched episodes and movies, get notifications about upcoming episodes and back it up and sync it across devices.
+
+Discover. Find shows with new episodes, which are popular or are similar to those you are watching. Explore movies currently in cinemas, released digitally or on disc.
+
+Backup and sync. Sign into SeriesGuide Cloud to backup and sync your shows, lists and movies.
+
+Integrates with Trakt. Connect your Trakt account to access your watchlist and collection, check in, rate and comment. Sync watched episodes and movies between apps and media centers supported by Trakt.
+
+Extensions. Add an extension button provided by a third-party app or build your own.
+
+Get the subscription to unlock all features and support SeriesGuide! You will get more list widget options, notifications for new episodes and more. 也能協助我們繼續研發更多新功能與修正錯誤。
+
+注意:並不能觀看影視劇集。 對於劇集,僅列出原始網路上的發佈日期。
+
+SeriesGuide X Unlock Key
+
+解鎖 SeriesGuide 所有功能的永久擁有權。 Requires both SeriesGuide and this key to be installed.
+
+All Access Sub
+解鎖所有功能,包含雲備份。
+
+Supporter Sub
+支援未來的開發和更新。 包含所有功能。
+
+Sponsor Sub
+為確保長期更新進行大額贊助。 包含所有功能。
diff --git a/widgets/build.gradle.kts b/widgets/build.gradle.kts
index 11003b1471..cba726130f 100644
--- a/widgets/build.gradle.kts
+++ b/widgets/build.gradle.kts
@@ -6,6 +6,11 @@ plugins {
val sgCompileSdk: Int by rootProject.extra
val sgMinSdk: Int by rootProject.extra
+tasks.withType(JavaCompile::class.java).configureEach {
+ // Suppress JDK 21 warning about deprecated, but not yet removed, source and target value 8 support
+ options.compilerArgs.add("-Xlint:-options")
+}
+
android {
namespace = "com.uwetrottmann.seriesguide.widgets"
compileSdk = sgCompileSdk
diff --git a/widgets/src/main/java/com/uwetrottmann/seriesguide/widgets/dragsortview/DragSortController.java b/widgets/src/main/java/com/uwetrottmann/seriesguide/widgets/dragsortview/DragSortController.java
index 21ef99a0cb..bf84440eaa 100644
--- a/widgets/src/main/java/com/uwetrottmann/seriesguide/widgets/dragsortview/DragSortController.java
+++ b/widgets/src/main/java/com/uwetrottmann/seriesguide/widgets/dragsortview/DragSortController.java
@@ -1,8 +1,9 @@
-// Copyright 2014, 2015, 2017-2019 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2014-2024 Uwe Trottmann
package com.uwetrottmann.seriesguide.widgets.dragsortview;
+import android.annotation.SuppressLint;
import android.graphics.Point;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
@@ -238,6 +239,8 @@ public boolean startDrag(int position, int deltaX, int deltaY) {
return mDragging;
}
+ // ClickableViewAccessibility: there is nothing to click
+ @SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouch(View v, MotionEvent ev) {
if (!mDslv.isDragEnabled() || mDslv.listViewIntercepted()) {
diff --git a/widgets/src/main/java/com/uwetrottmann/seriesguide/widgets/dragsortview/DragSortListView.java b/widgets/src/main/java/com/uwetrottmann/seriesguide/widgets/dragsortview/DragSortListView.java
index 0fb43e1ddb..5d4c1106e7 100644
--- a/widgets/src/main/java/com/uwetrottmann/seriesguide/widgets/dragsortview/DragSortListView.java
+++ b/widgets/src/main/java/com/uwetrottmann/seriesguide/widgets/dragsortview/DragSortListView.java
@@ -1,6 +1,6 @@
-// Copyright 2012 Carl Bauer
-// Copyright 2014, 2015, 2017-2019 Uwe Trottmann
// SPDX-License-Identifier: Apache-2.0
+// Copyright 2012 Carl Bauer
+// Copyright 2014-2024 Uwe Trottmann
package com.uwetrottmann.seriesguide.widgets.dragsortview;
@@ -1522,6 +1522,8 @@ public boolean stopDrag(boolean remove, float velocityX) {
}
}
+ // ClickableViewAccessibility: there is nothing to click
+ @SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mIgnoreTouchEvent) {