diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
new file mode 100644
index 00000000..f842d2f0
--- /dev/null
+++ b/.github/workflows/android.yml
@@ -0,0 +1,35 @@
+name: Android CI
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup JDK
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ - name: Test with Gradle
+ run: ./gradlew test
+
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Setup JDK
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+ - name: Verify Gradle
+ uses: gradle/actions/wrapper-validation@v4
+ - name: Build with Gradle
+ run: ./gradlew build
+ - name: Upload APK
+ uses: actions/upload-artifact@v4
+ with:
+ name: app-debug
+ path: app/build/outputs/apk/standard/debug/app-standard-debug.apk
diff --git a/.gitignore b/.gitignore
index 56e87749..06fd74a9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,6 @@
build/
.gradle/
local.properties
+.idea/
+*.iml
+app/fdroid/
diff --git a/README.md b/README.md
index 547193eb..03d495fc 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,16 @@
# Episodes
+
+[](https://hosted.weblate.org/engage/episodes/)
+[](https://f-droid.org/en/packages/com.redcoracle.episodes)
-Keep track of which episodes you've watched of your favourite TV shows.
+Keep track of which episodes you've watched of your favourite shows.
-TV Show information is supplied by TheTVDB.com.
+This product uses the [TMDB](https://www.themoviedb.org) API but is not endorsed or certified by TMDB.
## Contributing
-Contributions are very welcome. Please file bugs, fork the repository, and send pull requests.
-
-If you'd like to buy me a beer you can send bitcoins to 149XkMSs84ZyzNMqiQeJLt5DbPru16amwA
+Contributions are very welcome. Please file bugs, fork the repository, [translate](https://hosted.weblate.org/projects/episodes/) and send pull requests.
## License
-Licensed under the [GPLv3](http://www.gnu.org/licenses/gpl-3.0.txt) or later.
-
-## Google Play
-
-
-
-
+Copylefted libre software licensed [GPLv3](http://www.gnu.org/licenses/gpl-3.0.txt)+.
diff --git a/app/build.gradle b/app/build.gradle
index 6fc21ceb..0c5fe629 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,45 +1,82 @@
apply plugin: 'com.android.application'
-android {
- compileSdkVersion 21
- buildToolsVersion "22.0.1"
+def getCommitId = { ->
+ def gitOutput = new ByteArrayOutputStream()
+ exec {
+ commandLine 'git', 'rev-parse', '--short', 'HEAD'
+ standardOutput = gitOutput
+ }
+ return gitOutput.toString().trim()
+}
+android {
+ namespace 'com.redcoracle.episodes'
+ compileSdkVersion 33
defaultConfig {
- applicationId "org.jamienicol.episodes"
- minSdkVersion 11
- targetSdkVersion 21
- versionCode 11
- versionName "0.11"
+ minSdkVersion 21
+ targetSdkVersion 33
+ versionCode 25
+ versionName "0.16.1"
+ buildConfigField "String", "TMDB_KEY", "\"1553d2e4fa2912fc0953305d4d3e7c44\""
+ buildConfigField "String", "GIT_COMMIT_ID", "\"${getCommitId()}\""
}
+ flavorDimensions "flavor"
productFlavors {
- fdroid {
- applicationId = "org.jamienicol.episodes"
- }
- playstore {
- applicationId = "com.vindustries.episodes"
+ standard {
+ dimension "flavor"
+ applicationId = "com.redcoracle.episodes"
}
}
buildTypes {
debug {
applicationIdSuffix '.debug'
versionNameSuffix '-DEBUG'
+ multiDexEnabled true
}
release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ minifyEnabled true
+ shrinkResources true
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
- lintOptions {
+ lint {
abortOnError false
}
+ compileOptions {
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
+ }
+ if (project.hasProperty('signingStoreLocation') &&
+ project.hasProperty('signingStorePassword') &&
+ project.hasProperty('signingKeyAlias') &&
+ project.hasProperty('signingKeyPassword')) {
+ println "Found sign properties in gradle.properties! Signing build…"
+
+ signingConfigs {
+ release {
+ storeFile file(signingStoreLocation)
+ storePassword signingStorePassword
+ keyAlias signingKeyAlias
+ keyPassword signingKeyPassword
+ }
+ }
+
+ buildTypes.release.signingConfig = signingConfigs.release
+ } else {
+ buildTypes.release.signingConfig = null
+ }
}
dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile 'com.android.support:support-v4:22.2.0'
- compile 'com.android.support:appcompat-v7:22.2.0'
- compile 'com.android.support:design:22.2.0'
- compile 'com.android.support:recyclerview-v7:22.2.0'
- compile 'com.squareup.okhttp:okhttp:2.1.0'
- compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3'
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation 'androidx.appcompat:appcompat:1.4.2'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.preference:preference:1.2.0'
+ implementation 'androidx.recyclerview:recyclerview:1.2.1'
+ implementation 'com.google.android.material:material:1.6.1'
+ implementation 'com.github.bumptech.glide:glide:4.14.1'
+ implementation 'com.uwetrottmann.tmdb2:tmdb-java:2.8.1'
+ implementation 'org.apache.commons:commons-collections4:4.4'
+ debugImplementation 'com.android.support:multidex:2.0.1'
+ annotationProcessor 'com.github.bumptech.glide:compiler:4.14.1'
}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 45dc58a5..72740ea1 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -9,9 +9,23 @@
# Add any project specific keep options here:
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
+# Glide
+-keep public class * implements com.bumptech.glide.module.GlideModule
+-keep public class * extends com.bumptech.glide.module.AppGlideModule
+-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
+ **[] $VALUES;
+ public *;
+}
+
+# TMDB
+-keep class com.uwetrottmann.tmdb2.entities.** { *; }
+-keep class com.uwetrottmann.tmdb2.enumerations.** { *; }
+
+# OkHttp platform used only on JVM and when Conscrypt and other security providers are available.
+-dontwarn okhttp3.internal.platform.**
+-dontwarn org.conscrypt.**
+-dontwarn org.bouncycastle.**
+-dontwarn org.openjsse.**
+
+# R8 said to add these
+-dontwarn kotlin.coroutines.Continuation
diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100755
index 00000000..e2c420d6
--- /dev/null
+++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher.png b/app/src/debug/res/mipmap-hdpi/ic_launcher.png
old mode 100644
new mode 100755
index 579898fe..d211bdeb
Binary files a/app/src/debug/res/mipmap-hdpi/ic_launcher.png and b/app/src/debug/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_background.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_background.png
new file mode 100755
index 00000000..2c4aefef
Binary files /dev/null and b/app/src/debug/res/mipmap-hdpi/ic_launcher_background.png differ
diff --git a/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..90722c92
Binary files /dev/null and b/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher.png b/app/src/debug/res/mipmap-mdpi/ic_launcher.png
old mode 100644
new mode 100755
index 9b530e2f..5c3f69d2
Binary files a/app/src/debug/res/mipmap-mdpi/ic_launcher.png and b/app/src/debug/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_background.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_background.png
new file mode 100755
index 00000000..85ddadd2
Binary files /dev/null and b/app/src/debug/res/mipmap-mdpi/ic_launcher_background.png differ
diff --git a/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..fd92fee8
Binary files /dev/null and b/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
old mode 100644
new mode 100755
index df0a4758..9650cc2a
Binary files a/app/src/debug/res/mipmap-xhdpi/ic_launcher.png and b/app/src/debug/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_background.png
new file mode 100755
index 00000000..ea36b519
Binary files /dev/null and b/app/src/debug/res/mipmap-xhdpi/ic_launcher_background.png differ
diff --git a/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..5d7626d1
Binary files /dev/null and b/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
old mode 100644
new mode 100755
index 8a880ceb..9246046b
Binary files a/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_background.png
new file mode 100755
index 00000000..207597d9
Binary files /dev/null and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_background.png differ
diff --git a/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..d86cb12f
Binary files /dev/null and b/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 00000000..d138d265
Binary files /dev/null and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100755
index 00000000..1b38c1ce
Binary files /dev/null and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_background.png differ
diff --git a/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..af231731
Binary files /dev/null and b/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 18172eea..1688b8d1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,18 +1,12 @@
+ xmlns:android="http://schemas.android.com/apk/res/android">
-
-
-
-
-
+
+
+
+
+
+ android:launchMode="singleTop"
+ android:exported="true">
@@ -71,14 +66,6 @@
android:label="" >
-
-
-
-
-
-
diff --git a/app/src/main/java/org/jamienicol/episodes/AboutActivity.java b/app/src/main/java/com/redcoracle/episodes/AboutActivity.java
similarity index 77%
rename from app/src/main/java/org/jamienicol/episodes/AboutActivity.java
rename to app/src/main/java/com/redcoracle/episodes/AboutActivity.java
index f9e2ade7..ed89d4c4 100644
--- a/app/src/main/java/org/jamienicol/episodes/AboutActivity.java
+++ b/app/src/main/java/com/redcoracle/episodes/AboutActivity.java
@@ -15,14 +15,16 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.os.Bundle;
-import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
+import android.widget.TextView;
+
+import androidx.appcompat.app.AppCompatActivity;
public class AboutActivity
- extends ActionBarActivity
+ extends AppCompatActivity
{
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -30,6 +32,13 @@ public void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.about_activity);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ TextView versionInfoView = findViewById(R.id.version_view);
+ versionInfoView.setText(
+ getBaseContext().getString(
+ R.string.version,
+ BuildConfig.VERSION_NAME,
+ BuildConfig.GIT_COMMIT_ID));
}
@Override
diff --git a/app/src/main/java/org/jamienicol/episodes/AddShowPreviewActivity.java b/app/src/main/java/com/redcoracle/episodes/AddShowPreviewActivity.java
similarity index 91%
rename from app/src/main/java/org/jamienicol/episodes/AddShowPreviewActivity.java
rename to app/src/main/java/com/redcoracle/episodes/AddShowPreviewActivity.java
index 88660760..5c375683 100644
--- a/app/src/main/java/org/jamienicol/episodes/AddShowPreviewActivity.java
+++ b/app/src/main/java/com/redcoracle/episodes/AddShowPreviewActivity.java
@@ -15,18 +15,21 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.content.Intent;
import android.os.Bundle;
-import android.support.v4.app.FragmentTransaction;
-import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentTransaction;
+
+import com.redcoracle.episodes.tvdb.Show;
+
import java.util.List;
-import org.jamienicol.episodes.tvdb.Show;
public class AddShowPreviewActivity
- extends ActionBarActivity
+ extends AppCompatActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
diff --git a/app/src/main/java/org/jamienicol/episodes/AddShowPreviewFragment.java b/app/src/main/java/com/redcoracle/episodes/AddShowPreviewFragment.java
similarity index 88%
rename from app/src/main/java/org/jamienicol/episodes/AddShowPreviewFragment.java
rename to app/src/main/java/com/redcoracle/episodes/AddShowPreviewFragment.java
index 8dff6add..44a159bb 100644
--- a/app/src/main/java/org/jamienicol/episodes/AddShowPreviewFragment.java
+++ b/app/src/main/java/com/redcoracle/episodes/AddShowPreviewFragment.java
@@ -15,12 +15,9 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
-import android.content.Intent;
-import android.database.Cursor;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -28,11 +25,16 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+
+import com.redcoracle.episodes.services.AddShowTask;
+import com.redcoracle.episodes.services.AsyncTask;
+import com.redcoracle.episodes.tvdb.Show;
+
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
-import org.jamienicol.episodes.services.AddShowService;
-import org.jamienicol.episodes.tvdb.Show;
public class AddShowPreviewFragment
extends Fragment
@@ -110,10 +112,6 @@ public boolean onOptionsItemSelected(MenuItem item) {
}
private void addShow() {
- Intent intent = new Intent(getActivity(), AddShowService.class);
- intent.putExtra("tvdbId", show.getId());
- intent.putExtra("showName", show.getName());
-
- getActivity().startService(intent);
+ new AsyncTask().executeAsync(new AddShowTask(show.getId(), show.getName(), show.getLanguage()));
}
}
diff --git a/app/src/main/java/org/jamienicol/episodes/AddShowSearchActivity.java b/app/src/main/java/com/redcoracle/episodes/AddShowSearchActivity.java
similarity index 92%
rename from app/src/main/java/org/jamienicol/episodes/AddShowSearchActivity.java
rename to app/src/main/java/com/redcoracle/episodes/AddShowSearchActivity.java
index a504e3d0..32e917a5 100644
--- a/app/src/main/java/org/jamienicol/episodes/AddShowSearchActivity.java
+++ b/app/src/main/java/com/redcoracle/episodes/AddShowSearchActivity.java
@@ -15,17 +15,18 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.content.Intent;
import android.os.Bundle;
-import android.support.v4.app.FragmentTransaction;
-import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
import android.view.Window;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentTransaction;
+
public class AddShowSearchActivity
- extends ActionBarActivity
+ extends AppCompatActivity
{
@Override
public void onCreate(Bundle savedInstanceState)
diff --git a/app/src/main/java/org/jamienicol/episodes/AddShowSearchFragment.java b/app/src/main/java/com/redcoracle/episodes/AddShowSearchFragment.java
similarity index 59%
rename from app/src/main/java/org/jamienicol/episodes/AddShowSearchFragment.java
rename to app/src/main/java/com/redcoracle/episodes/AddShowSearchFragment.java
index 28d64019..c62aae9d 100644
--- a/app/src/main/java/org/jamienicol/episodes/AddShowSearchFragment.java
+++ b/app/src/main/java/com/redcoracle/episodes/AddShowSearchFragment.java
@@ -15,28 +15,30 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.app.Activity;
-import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
-import android.support.v4.app.ListFragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.AsyncTaskLoader;
-import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.ListFragment;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.AsyncTaskLoader;
+import androidx.loader.content.Loader;
+
+import com.redcoracle.episodes.tvdb.Client;
+import com.redcoracle.episodes.tvdb.Show;
+
import java.util.List;
-import org.jamienicol.episodes.db.ShowsProvider;
-import org.jamienicol.episodes.db.ShowsTable;
-import org.jamienicol.episodes.tvdb.Client;
-import org.jamienicol.episodes.tvdb.Show;
public class AddShowSearchFragment
extends ListFragment
@@ -53,39 +55,28 @@ public static AddShowSearchFragment newInstance(String query) {
}
@Override
- public View onCreateView(LayoutInflater inflater,
- ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.add_show_search_fragment,
- container,
- false);
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.add_show_search_fragment, container, false);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
- // if the loader exists and it's busy loading
- // then spin the progress bar.
- Loader loader = getLoaderManager().getLoader(0);
- if (loader != null) {
- getActivity().setProgressBarIndeterminateVisibility(loader.isStarted());
- }
-
String query = getArguments().getString("query");
Bundle loaderArgs = new Bundle();
loaderArgs.putString("query", query);
- getLoaderManager().initLoader(0, loaderArgs, this);
+ LoaderManager.getInstance(this).initLoader(0, loaderArgs, this);
}
+ @NonNull
@Override
public Loader> onCreateLoader(int id, Bundle args) {
- getActivity().setProgressBarIndeterminateVisibility(true);
- SearchLoader loader = new SearchLoader(getActivity(),
- args.getString("query"));
- return loader;
+ getActivity().findViewById(R.id.search_progress_bar).setVisibility(View.VISIBLE);
+ getActivity().findViewById(R.id.search_no_results_found).setVisibility(View.GONE);
+ return new SearchLoader(getActivity(), args.getString("query"));
}
@Override
@@ -94,12 +85,15 @@ public void onLoadFinished(Loader> loader, List data) {
results.setData(data);
Activity activity = getActivity();
- activity.setProgressBarIndeterminateVisibility(false);
-
SearchResultsAdapter adapter = null;
+
if (data != null) {
+ if (data.size() == 0) {
+ activity.findViewById(R.id.search_no_results_found).setVisibility(View.VISIBLE);
+ }
adapter = new SearchResultsAdapter(activity, data);
}
+ activity.findViewById(R.id.search_progress_bar).setVisibility(View.GONE);
setListAdapter(adapter);
}
@@ -107,17 +101,15 @@ public void onLoadFinished(Loader> loader, List data) {
public void onLoaderReset(Loader> loader) {
AddShowSearchResults results = AddShowSearchResults.getInstance();
results.setData(null);
-
setListAdapter(null);
}
- private static class SearchLoader
- extends AsyncTaskLoader>
- {
+ private static class SearchLoader extends AsyncTaskLoader> {
private final String query;
private List cachedResult;
+ private final SharedPreferences preferences = Preferences.getSharedPreferences();
- public SearchLoader(Context context, String query) {
+ SearchLoader(Context context, String query) {
super(context);
this.query = query;
@@ -126,9 +118,18 @@ public SearchLoader(Context context, String query) {
@Override
public List loadInBackground() {
- Client tvdbClient = new Client("25B864A8BC56AFAD");
+ Client tmdbClient = new Client();
+ String language = preferences.getString("pref_language", "en");
- List results = tvdbClient.searchShows(query);
+ List results = tmdbClient.searchShows(query, language);
+
+ // If there are no results, try searching all languages or substituting &
+ if (results.size() == 0 && query.contains(" and ")) {
+ results = tmdbClient.searchShows(query.replace(" and ", " & "), null);
+ }
+ if (results.size() == 0) {
+ results = tmdbClient.searchShows(query, null);
+ }
return results;
}
@@ -164,34 +165,27 @@ public void onReset() {
}
public void onListItemClick(ListView l, View v, int position, long id) {
- Intent intent = new Intent(getActivity(),
- AddShowPreviewActivity.class);
+ Intent intent = new Intent(getActivity(), AddShowPreviewActivity.class);
intent.putExtra("searchResultIndex", position);
startActivity(intent);
}
- private static class SearchResultsAdapter
- extends ArrayAdapter
- {
- private LayoutInflater inflater;
+ private static class SearchResultsAdapter extends ArrayAdapter {
+ private final LayoutInflater inflater;
- public SearchResultsAdapter(Context context, List objects) {
+ SearchResultsAdapter(Context context, List objects) {
super(context, 0, 0, objects);
-
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
+ @NonNull
+ @Override
+ public View getView(int position, View convertView, @NonNull ViewGroup parent) {
if (convertView == null) {
- convertView =
- inflater.inflate(R.layout.add_show_search_results_list_item,
- parent,
- false);
+ convertView = inflater.inflate(R.layout.add_show_search_results_list_item, parent, false);
}
- TextView textView =
- (TextView)convertView.findViewById(R.id.show_name_view);
+ TextView textView = convertView.findViewById(R.id.show_name_view);
textView.setText(getItem(position).getName());
return convertView;
diff --git a/app/src/main/java/org/jamienicol/episodes/AddShowSearchResults.java b/app/src/main/java/com/redcoracle/episodes/AddShowSearchResults.java
similarity index 93%
rename from app/src/main/java/org/jamienicol/episodes/AddShowSearchResults.java
rename to app/src/main/java/com/redcoracle/episodes/AddShowSearchResults.java
index 7603f1f1..04863bf4 100644
--- a/app/src/main/java/org/jamienicol/episodes/AddShowSearchResults.java
+++ b/app/src/main/java/com/redcoracle/episodes/AddShowSearchResults.java
@@ -15,10 +15,11 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
+
+import com.redcoracle.episodes.tvdb.Show;
import java.util.List;
-import org.jamienicol.episodes.tvdb.Show;
public class AddShowSearchResults
{
diff --git a/app/src/main/java/org/jamienicol/episodes/AutoRefreshHelper.java b/app/src/main/java/com/redcoracle/episodes/AutoRefreshHelper.java
similarity index 59%
rename from app/src/main/java/org/jamienicol/episodes/AutoRefreshHelper.java
rename to app/src/main/java/com/redcoracle/episodes/AutoRefreshHelper.java
index 8bb23c63..b2484e97 100644
--- a/app/src/main/java/org/jamienicol/episodes/AutoRefreshHelper.java
+++ b/app/src/main/java/com/redcoracle/episodes/AutoRefreshHelper.java
@@ -15,43 +15,36 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
-import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
-import android.os.SystemClock;
+import android.os.Build;
import android.preference.PreferenceManager;
-import android.support.v4.net.ConnectivityManagerCompat;
import android.util.Log;
-import org.jamienicol.episodes.db.ShowsTable;
-import org.jamienicol.episodes.db.ShowsProvider;
-import org.jamienicol.episodes.RefreshShowUtil;
-public class AutoRefreshHelper
- implements SharedPreferences.OnSharedPreferenceChangeListener
-{
- private static final String TAG = AutoRefreshHelper.class.getName();
+import androidx.core.net.ConnectivityManagerCompat;
+
+import com.redcoracle.episodes.services.AsyncTask;
+import com.redcoracle.episodes.services.RefreshAllShowsTask;
- private static final String KEY_PREF_AUTO_REFRESH_ENABLED =
- "pref_auto_refresh_enabled";
- private static final String KEY_PREF_AUTO_REFRESH_PERIOD =
- "pref_auto_refresh_period";
- private static final String KEY_PREF_AUTO_REFRESH_WIFI_ONLY =
- "pref_auto_refresh_wifi_only";
+public class AutoRefreshHelper implements SharedPreferences.OnSharedPreferenceChangeListener {
+ private static final String TAG = AutoRefreshHelper.class.getName();
- private static final String KEY_LAST_AUTO_REFRESH_TIME =
- "last_auto_refresh_time";
+ private static final String KEY_PREF_AUTO_REFRESH_ENABLED = "pref_auto_refresh_enabled";
+ private static final String KEY_PREF_AUTO_REFRESH_PERIOD = "pref_auto_refresh_period";
+ private static final String KEY_PREF_AUTO_REFRESH_WIFI_ONLY = "pref_auto_refresh_wifi_only";
+ private static final String KEY_LAST_AUTO_REFRESH_TIME = "last_auto_refresh_time";
+ private static final String KEY_PREF_CONFIRMED_BACKUP = "pref_confirmed_backup";
private static AutoRefreshHelper instance;
@@ -67,14 +60,13 @@ public AutoRefreshHelper(Context context) {
public static synchronized AutoRefreshHelper getInstance(Context context) {
if (instance == null) {
- instance = new AutoRefreshHelper(context);
+ instance = new AutoRefreshHelper(context.getApplicationContext());
}
return instance;
}
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
- String key) {
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KEY_PREF_AUTO_REFRESH_ENABLED)) {
onAutoRefreshEnabledChanged();
} else if (key.equals(KEY_PREF_AUTO_REFRESH_PERIOD)) {
@@ -101,8 +93,7 @@ private boolean getAutoRefreshEnabled() {
}
private long getAutoRefreshPeriod() {
- final String hours =
- preferences.getString(KEY_PREF_AUTO_REFRESH_PERIOD, "0");
+ final String hours = preferences.getString(KEY_PREF_AUTO_REFRESH_PERIOD, "0");
// convert hours to milliseconds
return Long.parseLong(hours) * 60 * 60 * 1000;
@@ -117,62 +108,62 @@ private long getPrevAutoRefreshTime() {
}
private void setPrevAutoRefreshTime(long time) {
- final SharedPreferences.Editor editor =
- preferences.edit();
+ final SharedPreferences.Editor editor = preferences.edit();
editor.putLong(KEY_LAST_AUTO_REFRESH_TIME, time);
editor.apply();
}
private boolean checkNetwork() {
- final ConnectivityManager connManager =
- (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final ConnectivityManager connManager = (ConnectivityManager)context.getSystemService(
+ Context.CONNECTIVITY_SERVICE
+ );
final NetworkInfo net = connManager.getActiveNetworkInfo();
final boolean connected = net != null && net.isConnected();
- final boolean metered =
- ConnectivityManagerCompat.isActiveNetworkMetered(connManager);
+ final boolean metered = ConnectivityManagerCompat.isActiveNetworkMetered(connManager);
final boolean unmeteredOnly = getAutoRefreshWifiOnly();
final boolean okay = connected && !(metered && unmeteredOnly);
- Log.i(TAG,
- String.format("connected=%b, metered=%b, unmeteredOnly=%b, checkNetwork() %s.",
- connected, metered, unmeteredOnly,
- okay ? "passes" : "fails"));
+ Log.i(TAG, String.format("connected=%b, metered=%b, unmeteredOnly=%b, checkNetwork() %s.",
+ connected, metered, unmeteredOnly, okay ? "passes" : "fails"));
return okay;
}
+ private boolean checkBackup() {
+ return preferences.getBoolean(KEY_PREF_CONFIRMED_BACKUP, false);
+ }
+
public void rescheduleAlarm() {
NetworkStateReceiver.disable(context);
- final AlarmManager alarmManager =
- (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
-
- final Intent intent =
- new Intent(context, AutoRefreshHelper.Service.class);
- final PendingIntent pendingIntent =
- PendingIntent.getService(context, 0, intent, 0);
+ final AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
+ final Intent intent = new Intent(context, AutoRefreshHelper.Service.class);
+ final int intentFlag;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ intentFlag = PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT;
+ } else {
+ intentFlag = PendingIntent.FLAG_UPDATE_CURRENT;
+ }
+ final PendingIntent pendingIntent = PendingIntent.getService(
+ context,
+ 0,
+ intent,
+ intentFlag
+ );
if (getAutoRefreshEnabled() && getAutoRefreshPeriod() != 0) {
- final long alarmTime =
- getPrevAutoRefreshTime() + getAutoRefreshPeriod();
-
+ final long alarmTime = getPrevAutoRefreshTime() + getAutoRefreshPeriod();
Log.i(TAG, String.format("Scheduling auto refresh alarm for %d.", alarmTime));
-
- alarmManager.set(AlarmManager.RTC,
- alarmTime,
- pendingIntent);
+ alarmManager.set(AlarmManager.RTC, alarmTime, pendingIntent);
} else {
Log.i(TAG, "Cancelling auto refresh alarm.");
-
alarmManager.cancel(pendingIntent);
}
}
- public static class Service
- extends IntentService
- {
+ public static class Service extends IntentService {
private static final String TAG = Service.class.getName();
public Service() {
@@ -181,60 +172,29 @@ public Service() {
@Override
protected void onHandleIntent(Intent intent) {
- final AutoRefreshHelper helper =
- AutoRefreshHelper.getInstance(getApplicationContext());
+ final AutoRefreshHelper helper = AutoRefreshHelper.getInstance(getApplicationContext());
- if (helper.checkNetwork()) {
+ if (helper.checkNetwork() && helper.checkBackup()) {
Log.i(TAG, "Refreshing all shows.");
-
- final ContentResolver contentResolver = getContentResolver();
- final Cursor cursor = getShowsCursor(contentResolver);
-
- while (cursor.moveToNext()) {
- final int showIdColumnIndex =
- cursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ID);
- final int showId = cursor.getInt(showIdColumnIndex);
-
- RefreshShowUtil.refreshShow(showId, contentResolver);
- }
-
+ new AsyncTask().executeAsync(new RefreshAllShowsTask());
helper.setPrevAutoRefreshTime(System.currentTimeMillis());
helper.rescheduleAlarm();
-
} else {
NetworkStateReceiver.enable(this);
}
}
- private static Cursor getShowsCursor(ContentResolver contentResolver) {
- final String[] projection = {
- ShowsTable.COLUMN_ID
- };
-
- final Cursor cursor =
- contentResolver.query(ShowsProvider.CONTENT_URI_SHOWS,
- projection,
- null,
- null,
- null);
-
- return cursor;
- }
}
- public static class BootReceiver
- extends BroadcastReceiver
- {
+ public static class BootReceiver extends BroadcastReceiver {
private static final String TAG = BootReceiver.class.getName();
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Log.i(TAG, "Boot received.");
-
// ensure that the auto-refresh alarm is scheduled.
- AutoRefreshHelper.getInstance(context.getApplicationContext())
- .rescheduleAlarm();
+ AutoRefreshHelper.getInstance(context.getApplicationContext()).rescheduleAlarm();
}
}
}
@@ -242,18 +202,13 @@ public void onReceive(Context context, Intent intent) {
// This receiver is disabled by default in the manifest.
// It should only be enabled when it needed, and should be
// disabled again straight afterwards.
- public static class NetworkStateReceiver
- extends BroadcastReceiver
- {
+ public static class NetworkStateReceiver extends BroadcastReceiver {
private static final String TAG = NetworkStateReceiver.class.getName();
@Override
public void onReceive(Context context, Intent intent) {
Log.i(TAG, "Network state change received.");
-
- final AutoRefreshHelper helper =
- AutoRefreshHelper.getInstance(context.getApplicationContext());
-
+ final AutoRefreshHelper helper = AutoRefreshHelper.getInstance(context.getApplicationContext());
if (helper.checkNetwork()) {
helper.rescheduleAlarm();
}
@@ -262,35 +217,35 @@ public void onReceive(Context context, Intent intent) {
public static void enable(Context context) {
final PackageManager packageManager = context.getPackageManager();
- final ComponentName receiver =
- new ComponentName(context, NetworkStateReceiver.class);
+ final ComponentName receiver = new ComponentName(context, NetworkStateReceiver.class);
if (packageManager.getComponentEnabledSetting(receiver) !=
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
Log.i(TAG, "Enabling network state receiver.");
}
packageManager.setComponentEnabledSetting(
- receiver,
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
- PackageManager.DONT_KILL_APP);
+ receiver,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP
+ );
}
public static void disable(Context context) {
final PackageManager packageManager = context.getPackageManager();
- final ComponentName receiver =
- new ComponentName(context, NetworkStateReceiver.class);
+ final ComponentName receiver = new ComponentName(context, NetworkStateReceiver.class);
if (packageManager.getComponentEnabledSetting(receiver) !=
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED) {
Log.i(TAG, "Disabling network state receiver.");
}
packageManager.setComponentEnabledSetting(
- receiver,
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
- PackageManager.DONT_KILL_APP);
+ receiver,
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP
+ );
}
}
}
diff --git a/app/src/main/java/org/jamienicol/episodes/EpisodeActivity.java b/app/src/main/java/com/redcoracle/episodes/EpisodeActivity.java
similarity index 90%
rename from app/src/main/java/org/jamienicol/episodes/EpisodeActivity.java
rename to app/src/main/java/com/redcoracle/episodes/EpisodeActivity.java
index d85420d6..3080bbe4 100644
--- a/app/src/main/java/org/jamienicol/episodes/EpisodeActivity.java
+++ b/app/src/main/java/com/redcoracle/episodes/EpisodeActivity.java
@@ -15,25 +15,27 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentStatePagerAdapter;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
-import android.support.v4.view.ViewPager;
-import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsProvider;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentStatePagerAdapter;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+import androidx.viewpager.widget.ViewPager;
+
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
public class EpisodeActivity
- extends ActionBarActivity
+ extends AppCompatActivity
implements LoaderManager.LoaderCallbacks
{
int initialEpisodeId;
diff --git a/app/src/main/java/org/jamienicol/episodes/EpisodeDetailsFragment.java b/app/src/main/java/com/redcoracle/episodes/EpisodeDetailsFragment.java
similarity index 95%
rename from app/src/main/java/org/jamienicol/episodes/EpisodeDetailsFragment.java
rename to app/src/main/java/com/redcoracle/episodes/EpisodeDetailsFragment.java
index ad563705..da9e609b 100644
--- a/app/src/main/java/org/jamienicol/episodes/EpisodeDetailsFragment.java
+++ b/app/src/main/java/com/redcoracle/episodes/EpisodeDetailsFragment.java
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
@@ -23,20 +23,23 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
+
import java.text.DateFormat;
import java.util.Date;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsProvider;
public class EpisodeDetailsFragment
extends Fragment
diff --git a/app/src/main/java/com/redcoracle/episodes/EpisodesApplication.java b/app/src/main/java/com/redcoracle/episodes/EpisodesApplication.java
new file mode 100644
index 00000000..0e1c29a6
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/EpisodesApplication.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes;
+
+import android.app.Application;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Build;
+import android.util.Log;
+
+import com.uwetrottmann.tmdb2.Tmdb;
+
+public class EpisodesApplication extends Application {
+ private static final String TAG = EpisodesApplication.class.getName();
+ private static EpisodesApplication instance;
+ private Tmdb tmdbClient;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ instance = this;
+
+ try {
+ this.tmdbClient = new Tmdb(BuildConfig.TMDB_KEY);
+ } catch (Exception e) {
+ Log.d(TAG, "Error initialising TmdbClient", e);
+ }
+
+ createNotificationChannel();
+ }
+
+ public static EpisodesApplication getInstance() {
+ return instance;
+ }
+
+ public Tmdb getTmdbClient() {
+ return this.tmdbClient;
+ }
+
+ private void createNotificationChannel(){
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ CharSequence name = getString(R.string.channel_name);
+ String description = getString(R.string.channel_description);
+ int importance = NotificationManager.IMPORTANCE_LOW;
+ NotificationChannel channel = new NotificationChannel("episodes_channel_id", name, importance);
+ channel.setDescription(description);
+ NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ notificationManager.createNotificationChannel(channel);
+ }
+ }
+}
diff --git a/app/src/main/java/org/jamienicol/episodes/EpisodesCounter.java b/app/src/main/java/com/redcoracle/episodes/EpisodesCounter.java
similarity index 89%
rename from app/src/main/java/org/jamienicol/episodes/EpisodesCounter.java
rename to app/src/main/java/com/redcoracle/episodes/EpisodesCounter.java
index 8bc5143f..1afe530e 100644
--- a/app/src/main/java/org/jamienicol/episodes/EpisodesCounter.java
+++ b/app/src/main/java/com/redcoracle/episodes/EpisodesCounter.java
@@ -15,14 +15,18 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
+import android.content.SharedPreferences;
import android.database.Cursor;
import android.util.SparseIntArray;
+
+import com.redcoracle.episodes.db.EpisodesTable;
+
+import java.util.Collections;
import java.util.Date;
import java.util.Set;
import java.util.TreeSet;
-import org.jamienicol.episodes.db.EpisodesTable;
public class EpisodesCounter
{
@@ -35,7 +39,15 @@ public class EpisodesCounter
public EpisodesCounter(String keyColumn) {
this.keyColumn = keyColumn;
- keys = new TreeSet();
+ SharedPreferences preferences = Preferences.getSharedPreferences();
+
+ if (preferences.getBoolean("reverse_sort_order", false)) {
+ keys = new TreeSet(Collections.reverseOrder());
+ } else {
+ keys = new TreeSet();
+ }
+
+ // keys = new TreeSet();
numAiredEpisodesMap = new SparseIntArray();
numWatchedEpisodesMap = new SparseIntArray();
numUpcomingEpisodesMap = new SparseIntArray();
diff --git a/app/src/main/java/org/jamienicol/episodes/EpisodesListFragment.java b/app/src/main/java/com/redcoracle/episodes/EpisodesListFragment.java
similarity index 95%
rename from app/src/main/java/org/jamienicol/episodes/EpisodesListFragment.java
rename to app/src/main/java/com/redcoracle/episodes/EpisodesListFragment.java
index 48067e7a..838cbd2d 100644
--- a/app/src/main/java/org/jamienicol/episodes/EpisodesListFragment.java
+++ b/app/src/main/java/com/redcoracle/episodes/EpisodesListFragment.java
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.app.Activity;
import android.content.AsyncQueryHandler;
@@ -25,11 +25,6 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.ListFragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.CursorAdapter;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -37,10 +32,18 @@
import android.widget.CompoundButton;
import android.widget.ListView;
import android.widget.TextView;
+
+import androidx.cursoradapter.widget.CursorAdapter;
+import androidx.fragment.app.ListFragment;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
+
import java.text.DateFormat;
import java.util.Date;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsProvider;
public class EpisodesListFragment
extends ListFragment
diff --git a/app/src/main/java/com/redcoracle/episodes/FileUtilities.java b/app/src/main/java/com/redcoracle/episodes/FileUtilities.java
new file mode 100644
index 00000000..d771822e
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/FileUtilities.java
@@ -0,0 +1,40 @@
+package com.redcoracle.episodes;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.OpenableColumns;
+
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class FileUtilities {
+ public static String get_suggested_filename() {
+ final Date today = new Date();
+ final DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HHmm", Locale.getDefault());
+ return String.format("episodes_%s.db", formatter.format(today));
+ }
+
+ public static String uri_to_filename(Context context, Uri uri) {
+ Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
+ int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ cursor.moveToFirst();
+ String filename = cursor.getString(nameIndex);
+ cursor.close();
+ return filename;
+ }
+
+ public static void copy_file(FileChannel source, FileChannel destination) {
+ try {
+ destination.transferFrom(source, 0, source.size());
+ source.close();
+ destination.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/MainActivity.java b/app/src/main/java/com/redcoracle/episodes/MainActivity.java
new file mode 100644
index 00000000..59d8f303
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/MainActivity.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2012-2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.SearchView;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.MenuItemCompat;
+import androidx.fragment.app.FragmentManager;
+
+import com.bumptech.glide.Glide;
+import com.redcoracle.episodes.db.DatabaseOpenHelper;
+import com.redcoracle.episodes.db.ShowsProvider;
+import com.redcoracle.episodes.services.AsyncTask;
+import com.redcoracle.episodes.services.BackupTask;
+import com.redcoracle.episodes.services.RestoreTask;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+
+public class MainActivity
+ extends AppCompatActivity
+ implements ShowsListFragment.OnShowSelectedListener,
+ SelectBackupDialog.OnBackupSelectedListener,
+ ActivityCompat.OnRequestPermissionsResultCallback {
+
+ private static Context context;
+ private static final int WRITE_REQUEST_CODE = 0;
+ private static final int READ_REQUEST_CODE = 1;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_activity);
+
+ MainActivity.context = getApplicationContext();
+ AutoRefreshHelper.getInstance(getApplicationContext()).rescheduleAlarm();
+ }
+
+ public static Context getAppContext() {
+ return MainActivity.context;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ final MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.main, menu);
+
+ final MenuItem menuItem = menu.findItem(R.id.menu_add_new_show);
+ final SearchView addShow =
+ (SearchView)MenuItemCompat.getActionView(menuItem);
+ addShow.setQueryHint(getString(R.string.menu_add_show_search_hint));
+ addShow.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
+ @Override
+ public boolean onQueryTextChange(String query) {
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ final Intent intent =
+ new Intent(MainActivity.this,
+ AddShowSearchActivity.class);
+ intent.putExtra("query", query);
+ startActivity(intent);
+ MenuItemCompat.collapseActionView(menuItem);
+ return true;
+ }
+ });
+
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_back_up:
+ back_up();
+ return true;
+
+ case R.id.menu_restore:
+ restore();
+ return true;
+
+ case R.id.menu_settings:
+ showSettings();
+ return true;
+
+ case R.id.menu_about:
+ showAbout();
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public void onShowSelected(int showId) {
+ final Intent intent = new Intent(this, ShowActivity.class);
+ intent.putExtra("showId", showId);
+ startActivity(intent);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ }
+
+ private boolean hasStoragePermission() {
+ return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private void back_up() {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("application/x-sqlite3");
+ intent.putExtra(Intent.EXTRA_TITLE, FileUtilities.get_suggested_filename());
+ startActivityForResult(intent, WRITE_REQUEST_CODE);
+ } else {
+ // For now, keep the existing functionality on pre-API19
+ if (hasStoragePermission()) {
+ new AsyncTask().executeAsync(new BackupTask(FileUtilities.get_suggested_filename()));
+ }
+ }
+ }
+
+ private void restore() {
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
+ Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setType("application/x-sqlite3");
+ // On API 31 the file was not selectable without this
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"application/octet-stream"});
+ startActivityForResult(intent, READ_REQUEST_CODE);
+ } else {
+ // For now, keep the existing functionality on pre-API19
+ if (hasStoragePermission()) {
+ final FragmentManager fm = getSupportFragmentManager();
+ final SelectBackupDialog dialog = new SelectBackupDialog();
+ dialog.show(fm, "select_backup_dialog");
+ }
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ try {
+ if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ Uri uri = data.getData();
+ FileUtilities.copy_file(
+ new FileInputStream(this.getDatabasePath(DatabaseOpenHelper.getDbName())).getChannel(),
+ new FileOutputStream(getContentResolver().openFileDescriptor(uri, "w").getFileDescriptor()).getChannel()
+ );
+ Toast.makeText(
+ this,
+ String.format(this.getString(R.string.back_up_success_message), FileUtilities.uri_to_filename(this, uri)),
+ Toast.LENGTH_LONG
+ ).show();
+ } else if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
+ Uri uri = data.getData();
+ FileUtilities.copy_file(
+ new FileInputStream(getContentResolver().openFileDescriptor(uri, "r").getFileDescriptor()).getChannel(),
+ new FileOutputStream(this.getDatabasePath(DatabaseOpenHelper.getDbName())).getChannel()
+ );
+ ShowsProvider.reloadDatabase(this);
+ android.os.AsyncTask.execute(() -> Glide.get(getApplicationContext()).clearDiskCache());
+ Toast.makeText(this, this.getString(R.string.restore_success_message), Toast.LENGTH_LONG).show();
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onBackupSelected(String backupFilename) {
+ new AsyncTask().executeAsync(new RestoreTask(backupFilename));
+ }
+
+ private void showSettings() {
+ final Intent intent = new Intent(this, SettingsActivity.class);
+ startActivity(intent);
+ }
+
+ private void showAbout() {
+ final Intent intent = new Intent(this, AboutActivity.class);
+ startActivity(intent);
+ }
+}
diff --git a/app/src/main/java/org/jamienicol/episodes/NextEpisodeFragment.java b/app/src/main/java/com/redcoracle/episodes/NextEpisodeFragment.java
similarity index 95%
rename from app/src/main/java/org/jamienicol/episodes/NextEpisodeFragment.java
rename to app/src/main/java/com/redcoracle/episodes/NextEpisodeFragment.java
index 8c3c0495..f21d126b 100644
--- a/app/src/main/java/org/jamienicol/episodes/NextEpisodeFragment.java
+++ b/app/src/main/java/com/redcoracle/episodes/NextEpisodeFragment.java
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
@@ -23,10 +23,6 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -34,10 +30,17 @@
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
+
import java.text.DateFormat;
import java.util.Date;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsProvider;
public class NextEpisodeFragment
extends Fragment
diff --git a/app/src/main/java/com/redcoracle/episodes/Preferences.java b/app/src/main/java/com/redcoracle/episodes/Preferences.java
new file mode 100644
index 00000000..5cc79ca6
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/Preferences.java
@@ -0,0 +1,12 @@
+package com.redcoracle.episodes;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+public class Preferences {
+ public static SharedPreferences getSharedPreferences() {
+ Context context = MainActivity.getAppContext();
+ return PreferenceManager.getDefaultSharedPreferences(context);
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/RefreshShowUtil.java b/app/src/main/java/com/redcoracle/episodes/RefreshShowUtil.java
new file mode 100644
index 00000000..2ba8a217
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/RefreshShowUtil.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
+import com.redcoracle.episodes.db.ShowsTable;
+import com.redcoracle.episodes.tvdb.Client;
+import com.redcoracle.episodes.tvdb.Episode;
+import com.redcoracle.episodes.tvdb.Show;
+
+import org.apache.commons.collections4.map.MultiKeyMap;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+public class RefreshShowUtil {
+ private static final String TAG = RefreshShowUtil.class.getName();
+
+ public static void refreshShow(int showId, ContentResolver contentResolver) {
+ Log.i(TAG, String.format("Refreshing show %d", showId));
+
+ final Client tmdbClient = new Client();
+ SharedPreferences preferences = Preferences.getSharedPreferences();
+
+ final String showLanguage = preferences.getString("pref_language", "en");
+ final HashMap showIds = getShowIds(showId, contentResolver);
+ final Show show = tmdbClient.getShow(showIds, showLanguage);
+
+ if (show != null) {
+ updateShow(showId, show, contentResolver);
+ if (show.getEpisodes() != null) {
+ updateEpisodes(showId, show.getEpisodes(), contentResolver);
+ }
+ }
+ }
+
+ private static HashMap getShowIds(int showId, ContentResolver contentResolver) {
+ final Uri showUri = Uri.withAppendedPath(ShowsProvider.CONTENT_URI_SHOWS, String.valueOf(showId));
+ final String[] projection = {
+ ShowsTable.COLUMN_TVDB_ID,
+ ShowsTable.COLUMN_TMDB_ID,
+ ShowsTable.COLUMN_IMDB_ID
+ };
+ final Cursor showCursor = contentResolver.query(showUri, projection, null, null, null);
+ showCursor.moveToFirst();
+ final int tvdbIdColumnIndex = showCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_TVDB_ID);
+ final int tmdbIdColumnIndex = showCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_TMDB_ID);
+ final int imdbIdColumnIndex = showCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_IMDB_ID);
+ HashMap showIds = new HashMap() {{
+ put("tvdbId", showCursor.getString(tvdbIdColumnIndex));
+ put("tmdbId", showCursor.getString(tmdbIdColumnIndex));
+ put("imdbId", showCursor.getString(imdbIdColumnIndex));
+ }};
+ showCursor.close();
+ return showIds;
+ }
+
+ private static void updateShow(int showId, Show show, ContentResolver contentResolver) {
+ final ContentValues showValues = new ContentValues();
+ if (show.getTvdbId() != 0) {
+ showValues.put(ShowsTable.COLUMN_TVDB_ID, show.getTvdbId());
+ }
+ showValues.put(ShowsTable.COLUMN_TMDB_ID, show.getTmdbId());
+ showValues.put(ShowsTable.COLUMN_IMDB_ID, show.getImdbId());
+ showValues.put(ShowsTable.COLUMN_NAME, show.getName());
+ showValues.put(ShowsTable.COLUMN_LANGUAGE, show.getLanguage());
+ showValues.put(ShowsTable.COLUMN_OVERVIEW, show.getOverview());
+ if (show.getFirstAired() != null) {
+ showValues.put(ShowsTable.COLUMN_FIRST_AIRED, show.getFirstAired().getTime() / 1000);
+ }
+ showValues.put(ShowsTable.COLUMN_BANNER_PATH, show.getBannerPath());
+ showValues.put(ShowsTable.COLUMN_FANART_PATH, show.getFanartPath());
+ showValues.put(ShowsTable.COLUMN_POSTER_PATH, show.getPosterPath());
+
+ final Uri showUri = Uri.withAppendedPath(ShowsProvider.CONTENT_URI_SHOWS, String.valueOf(showId));
+ contentResolver.update(showUri, showValues, null, null);
+ }
+
+ private static void updateEpisodes(int showId, List episodes, ContentResolver contentResolver) {
+ // TODO: likely performance gains to be had in here
+ final MultiKeyMap seasonPairMap = new MultiKeyMap();
+ final HashSet seen = new HashSet<>();
+ final SparseArray episodeMap = new SparseArray<>();
+ final ArrayList updates = new ArrayList<>();
+
+ for (Episode episode : episodes) {
+ episodeMap.append(episode.getTmdbId(), episode);
+ seasonPairMap.put(episode.getSeasonNumber(), episode.getEpisodeNumber(), episode);
+ }
+
+ final Cursor cursor = getEpisodesCursor(showId, contentResolver);
+
+ while (cursor.moveToNext()) {
+ final int idColumnIndex = cursor.getColumnIndexOrThrow(EpisodesTable.COLUMN_ID);
+ final int episodeId = cursor.getInt(idColumnIndex);
+ final int tmdbColumnIndex = cursor.getColumnIndexOrThrow(EpisodesTable.COLUMN_TMDB_ID);
+ final int episodeTmdbId = cursor.getInt(tmdbColumnIndex);
+
+ Episode episode = episodeMap.get(episodeTmdbId);
+ final Uri episodeUri = Uri.withAppendedPath(ShowsProvider.CONTENT_URI_EPISODES, String.valueOf(episodeId));
+
+ if (episode == null) {
+ // Unable to find episode by ID; try season/episode pair instead.
+ // I think this should only happen when a show needs to migrate from TVDB->TMDB
+ episode = (Episode) seasonPairMap.get(
+ cursor.getInt(cursor.getColumnIndexOrThrow(EpisodesTable.COLUMN_SEASON_NUMBER)),
+ cursor.getInt(cursor.getColumnIndexOrThrow(EpisodesTable.COLUMN_EPISODE_NUMBER))
+ );
+ if (episode != null) {
+ Log.d(TAG, String.format("Matched by season/episode number: %s", episodeId));
+ if (seen.contains(episode.identifier())) {
+ // Already matched a different episode by season/episode pair,
+ // so this will fail on insert and should be deleted instead.
+ Log.d(TAG, String.format("Deleting duplicate episode %s (%s)",episode.identifier(), episodeId));
+ contentResolver.delete(episodeUri, null, null);
+ } else {
+ seen.add(episode.identifier());
+ episodes.remove(episode);
+ continue;
+ }
+ }
+ } else if (seen.contains(episode.identifier())) {
+ Log.d(TAG, String.format("Deleting previously seen episode %s (%s)", episode.identifier(), episode.getId()));
+ contentResolver.delete(episodeUri, null, null);
+ continue;
+ } else {
+ Log.d(TAG, String.format("Found match by TMDB ID: %s", episodeId));
+ seen.add(episode.identifier());
+ }
+
+ if (episode == null) {
+ Log.i(TAG, String.format("No matches found. Deleting episode: %d", episodeId));
+ contentResolver.delete(episodeUri, null, null);
+ } else {
+ final ContentValues epValues = new ContentValues();
+ epValues.put(EpisodesTable.COLUMN_ID, episodeId);
+ epValues.put(EpisodesTable.COLUMN_SHOW_ID, showId);
+ epValues.put(EpisodesTable.COLUMN_TVDB_ID, episode.getTvdbId());
+ epValues.put(EpisodesTable.COLUMN_TMDB_ID, episode.getTmdbId());
+ epValues.put(EpisodesTable.COLUMN_IMDB_ID, episode.getImdbId());
+ epValues.put(EpisodesTable.COLUMN_NAME, episode.getName());
+ epValues.put(EpisodesTable.COLUMN_LANGUAGE, episode.getLanguage());
+ epValues.put(EpisodesTable.COLUMN_OVERVIEW, episode.getOverview());
+ epValues.put(EpisodesTable.COLUMN_EPISODE_NUMBER, episode.getEpisodeNumber());
+ epValues.put(EpisodesTable.COLUMN_SEASON_NUMBER, episode.getSeasonNumber());
+ if (episode.getFirstAired() != null) {
+ epValues.put(EpisodesTable.COLUMN_FIRST_AIRED, episode.getFirstAired().getTime() / 1000);
+ }
+
+ Log.i(TAG, String.format("Updating episode %d.", episodeId));
+ updates.add(epValues);
+
+ /* remove episode from list of episodes
+ * returned by tvdb. by the end of this function
+ * this list will only contain new episodes */
+ episodes.remove(episode);
+ }
+ }
+ cursor.close();
+ contentResolver.bulkInsert(ShowsProvider.CONTENT_URI_EPISODES, updates.toArray(new ContentValues[0]));
+
+
+ for (Episode episode : episodes) {
+ final ContentValues epValues = new ContentValues();
+ epValues.put(EpisodesTable.COLUMN_SHOW_ID, showId);
+ epValues.put(EpisodesTable.COLUMN_TVDB_ID, episode.getTvdbId());
+ epValues.put(EpisodesTable.COLUMN_TMDB_ID, episode.getTmdbId());
+ epValues.put(EpisodesTable.COLUMN_IMDB_ID, episode.getImdbId());
+ epValues.put(EpisodesTable.COLUMN_NAME, episode.getName());
+ epValues.put(EpisodesTable.COLUMN_LANGUAGE, episode.getLanguage());
+ epValues.put(EpisodesTable.COLUMN_OVERVIEW, episode.getOverview());
+ epValues.put(EpisodesTable.COLUMN_EPISODE_NUMBER, episode.getEpisodeNumber());
+ epValues.put(EpisodesTable.COLUMN_SEASON_NUMBER, episode.getSeasonNumber());
+ if (episode.getFirstAired() != null) {
+ epValues.put(EpisodesTable.COLUMN_FIRST_AIRED, episode.getFirstAired().getTime() / 1000);
+ }
+
+ contentResolver.insert(ShowsProvider.CONTENT_URI_EPISODES, epValues);
+ }
+ }
+
+ private static Cursor getEpisodesCursor(int showId, ContentResolver contentResolver) {
+ final String[] projection = {
+ EpisodesTable.COLUMN_ID,
+ EpisodesTable.COLUMN_TVDB_ID,
+ EpisodesTable.COLUMN_TMDB_ID,
+ EpisodesTable.COLUMN_IMDB_ID,
+ EpisodesTable.COLUMN_SEASON_NUMBER,
+ EpisodesTable.COLUMN_EPISODE_NUMBER
+ };
+ final String selection = String.format("%s=?", EpisodesTable.COLUMN_SHOW_ID);
+ final String[] selectionArgs = {
+ String.valueOf(showId)
+ };
+
+ return contentResolver.query(ShowsProvider.CONTENT_URI_EPISODES, projection, selection, selectionArgs, null);
+ }
+}
diff --git a/app/src/main/java/org/jamienicol/episodes/SeasonActivity.java b/app/src/main/java/com/redcoracle/episodes/SeasonActivity.java
similarity index 74%
rename from app/src/main/java/org/jamienicol/episodes/SeasonActivity.java
rename to app/src/main/java/com/redcoracle/episodes/SeasonActivity.java
index 8fec6c04..f7b13bc4 100644
--- a/app/src/main/java/org/jamienicol/episodes/SeasonActivity.java
+++ b/app/src/main/java/com/redcoracle/episodes/SeasonActivity.java
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
@@ -24,21 +24,26 @@
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.FragmentTransaction;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
-import android.support.v7.app.ActionBar;
-import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsProvider;
-import org.jamienicol.episodes.db.ShowsTable;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
+import com.redcoracle.episodes.db.ShowsTable;
+
+import java.util.ArrayList;
+import java.util.Date;
public class SeasonActivity
- extends ActionBarActivity
+ extends AppCompatActivity
implements LoaderManager.LoaderCallbacks,
EpisodesListFragment.OnEpisodeSelectedListener
{
@@ -65,7 +70,7 @@ public void onCreate(Bundle savedInstanceState)
final Bundle loaderArgs = new Bundle();
loaderArgs.putInt("showId", showId);
- getSupportLoaderManager().initLoader(0, loaderArgs, this);
+ LoaderManager.getInstance(this).initLoader(0, loaderArgs, this);
final ActionBar actionBar = getSupportActionBar();
if (seasonNumber == 0) {
@@ -130,28 +135,24 @@ public void onLoaderReset(Loader loader) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
+ int item_id = item.getItemId();
+ if (item_id == android.R.id.home) {
finish();
return true;
-
- case R.id.menu_mark_season_watched:
+ } else if (item_id == R.id.menu_mark_season_watched) {
markSeasonWatched(true);
return true;
-
- case R.id.menu_mark_season_not_watched:
+ } else if (item_id == R.id.menu_mark_season_not_watched) {
markSeasonWatched(false);
return true;
-
- default:
+ } else {
return super.onOptionsItemSelected(item);
}
}
@Override
public void onEpisodeSelected(int episodeId) {
- final Intent intent = new Intent(this,
- EpisodeActivity.class);
+ final Intent intent = new Intent(this, EpisodeActivity.class);
intent.putExtra("showId", showId);
intent.putExtra("seasonNumber", seasonNumber);
intent.putExtra("initialEpisodeId", episodeId);
@@ -160,24 +161,38 @@ public void onEpisodeSelected(int episodeId) {
private void markSeasonWatched(boolean watched) {
final ContentResolver contentResolver = getContentResolver();
- final AsyncQueryHandler handler =
- new AsyncQueryHandler(contentResolver) {};
+ final AsyncQueryHandler handler = new AsyncQueryHandler(contentResolver) {};
final ContentValues epValues = new ContentValues();
+ final Date now = new Date();
epValues.put(EpisodesTable.COLUMN_WATCHED, watched);
- final String selection =
- String.format("%s=? AND %s=?",
- EpisodesTable.COLUMN_SHOW_ID,
- EpisodesTable.COLUMN_SEASON_NUMBER);
- final String[] selectionArgs = {
- String.valueOf(showId),
- String.valueOf(seasonNumber)
+ String selection = String.format(
+ "%s=? AND %s=?",
+ EpisodesTable.COLUMN_SHOW_ID,
+ EpisodesTable.COLUMN_SEASON_NUMBER
+ );
+ ArrayList selectionArgs = new ArrayList(){
+ {
+ add(String.valueOf(showId));
+ add(String.valueOf(seasonNumber));
+ }
};
+ if (watched) {
+ // Only mark episodes that have aired.
+ selection = String.format(
+ "%s AND %s <= ? AND %s IS NOT NULL",
+ selection,
+ EpisodesTable.COLUMN_FIRST_AIRED,
+ EpisodesTable.COLUMN_FIRST_AIRED
+ );
+ selectionArgs.add(String.valueOf(now.getTime() / 1000));
+ }
+
handler.startUpdate(0,
null,
ShowsProvider.CONTENT_URI_EPISODES,
epValues,
selection,
- selectionArgs);
+ selectionArgs.toArray(new String[0]));
}
}
diff --git a/app/src/main/java/org/jamienicol/episodes/SeasonsListFragment.java b/app/src/main/java/com/redcoracle/episodes/SeasonsListFragment.java
similarity index 94%
rename from app/src/main/java/org/jamienicol/episodes/SeasonsListFragment.java
rename to app/src/main/java/com/redcoracle/episodes/SeasonsListFragment.java
index 667002d6..c9b12183 100644
--- a/app/src/main/java/org/jamienicol/episodes/SeasonsListFragment.java
+++ b/app/src/main/java/com/redcoracle/episodes/SeasonsListFragment.java
@@ -15,25 +15,27 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
-import android.support.v7.widget.LinearLayoutManager;
-import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsProvider;
+
+import androidx.fragment.app.Fragment;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
public class SeasonsListFragment
extends Fragment
diff --git a/app/src/main/java/org/jamienicol/episodes/SelectBackupDialog.java b/app/src/main/java/com/redcoracle/episodes/SelectBackupDialog.java
similarity index 53%
rename from app/src/main/java/org/jamienicol/episodes/SelectBackupDialog.java
rename to app/src/main/java/com/redcoracle/episodes/SelectBackupDialog.java
index 993d36bf..f237dd13 100644
--- a/app/src/main/java/org/jamienicol/episodes/SelectBackupDialog.java
+++ b/app/src/main/java/com/redcoracle/episodes/SelectBackupDialog.java
@@ -15,45 +15,42 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.content.DialogInterface;
+import android.content.Context;
import android.os.Bundle;
-import android.support.v4.app.DialogFragment;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+
import java.io.File;
import java.util.Arrays;
-import java.util.Comparator;
-import org.jamienicol.episodes.db.BackUpRestoreHelper;
-public class SelectBackupDialog
- extends DialogFragment
-{
+public class SelectBackupDialog extends DialogFragment {
public interface OnBackupSelectedListener {
- public void onBackupSelected(String backupFilename);
+ void onBackupSelected(String backupFilename);
}
private OnBackupSelectedListener onBackupSelectedListener;
+ private Context context;
@Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- try {
- onBackupSelectedListener = (OnBackupSelectedListener)activity;
- } catch (ClassCastException e) {
- final String message =
- String.format("%s must implement OnBackupSelectedListener",
- activity.toString());
- throw new ClassCastException(message);
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ this.context = context;
+ Activity activity;
+ if (context instanceof Activity) {
+ activity = (Activity) context;
+ onBackupSelectedListener = (OnBackupSelectedListener) activity;
}
}
+ @NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- final AlertDialog.Builder builder =
- new AlertDialog.Builder(getActivity());
+ final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final File[] backups = getBackupFiles();
@@ -65,47 +62,35 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
}
private File[] getBackupFiles() {
- final File[] files = BackUpRestoreHelper.getBackupDir().listFiles();
+ final File[] files = new File(this.context.getExternalFilesDir(null), "episodes").listFiles();
if (files != null) {
- Arrays.sort(files, new Comparator() {
- public int compare(File lhs, File rhs) {
- return Long.valueOf(rhs.lastModified()).
- compareTo(lhs.lastModified());
- }
- });
+ Arrays.sort(files, (lhs, rhs) -> Long.compare(rhs.lastModified(), lhs.lastModified()));
return files;
} else {
return null;
}
}
- private Dialog createDialogBackups(AlertDialog.Builder builder,
- final File[] backups) {
+ private Dialog createDialogBackups(AlertDialog.Builder builder, final File[] backups) {
final String[] names = new String[backups.length];
for (int i = 0; i < backups.length; i++) {
names[i] = backups[i].getName();
}
builder.setTitle(R.string.restore_dialog_title)
- .setItems(names, new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- final String path = backups[which].getPath();
- onBackupSelectedListener.onBackupSelected(path);
- }
+ .setItems(names, (dialog, which) -> {
+ final String path = backups[which].getPath();
+ onBackupSelectedListener.onBackupSelected(path);
});
return builder.create();
}
private Dialog createDialogNoBackups(AlertDialog.Builder builder) {
- final String message =
- getActivity().getString(R.string.restore_dialog_no_backups_message,
- BackUpRestoreHelper.getBackupDir());
-
- builder.setTitle(R.string.restore_dialog_title)
- .setMessage(message);
-
+ final File directory = new File(this.context.getExternalFilesDir(null), "episodes");
+ final String message = getActivity().getString(R.string.restore_dialog_no_backups_message, directory);
+ builder.setTitle(R.string.restore_dialog_title).setMessage(message);
return builder.create();
}
}
diff --git a/app/src/main/java/org/jamienicol/episodes/SettingsActivity.java b/app/src/main/java/com/redcoracle/episodes/SettingsActivity.java
similarity index 77%
rename from app/src/main/java/org/jamienicol/episodes/SettingsActivity.java
rename to app/src/main/java/com/redcoracle/episodes/SettingsActivity.java
index faae3f51..724dd5f9 100644
--- a/app/src/main/java/org/jamienicol/episodes/SettingsActivity.java
+++ b/app/src/main/java/com/redcoracle/episodes/SettingsActivity.java
@@ -15,19 +15,21 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.os.Bundle;
-import android.support.v7.app.ActionBarActivity;
-public class SettingsActivity
- extends ActionBarActivity
-{
+import androidx.appcompat.app.AppCompatActivity;
+
+public class SettingsActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
-
+ getSupportFragmentManager()
+ .beginTransaction()
+ .replace(R.id.settings_fragment, new SettingsFragment())
+ .commit();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
diff --git a/app/src/main/java/org/jamienicol/episodes/SettingsFragment.java b/app/src/main/java/com/redcoracle/episodes/SettingsFragment.java
similarity index 72%
rename from app/src/main/java/org/jamienicol/episodes/SettingsFragment.java
rename to app/src/main/java/com/redcoracle/episodes/SettingsFragment.java
index 9ff49803..a7ede6e4 100644
--- a/app/src/main/java/org/jamienicol/episodes/SettingsFragment.java
+++ b/app/src/main/java/com/redcoracle/episodes/SettingsFragment.java
@@ -15,17 +15,15 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
-import android.preference.PreferenceFragment;
import android.os.Bundle;
-public class SettingsFragment
- extends PreferenceFragment
-{
+import androidx.preference.PreferenceFragmentCompat;
+
+public class SettingsFragment extends PreferenceFragmentCompat {
@Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- addPreferencesFromResource(R.xml.preferences);
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ setPreferencesFromResource(R.xml.preferences, rootKey);
}
}
diff --git a/app/src/main/java/org/jamienicol/episodes/ShowActivity.java b/app/src/main/java/com/redcoracle/episodes/ShowActivity.java
similarity index 67%
rename from app/src/main/java/org/jamienicol/episodes/ShowActivity.java
rename to app/src/main/java/com/redcoracle/episodes/ShowActivity.java
index 3e4f1d46..23dcfaad 100644
--- a/app/src/main/java/org/jamienicol/episodes/ShowActivity.java
+++ b/app/src/main/java/com/redcoracle/episodes/ShowActivity.java
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
@@ -24,35 +24,40 @@
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
+import android.graphics.PorterDuff;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
-import android.support.design.widget.TabLayout;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.app.FragmentPagerAdapter;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
-import android.support.v4.view.ViewPager;
-import android.support.v7.app.ActionBarActivity;
-import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
-import com.nostra13.universalimageloader.core.DisplayImageOptions;
-import com.nostra13.universalimageloader.core.ImageLoader;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsProvider;
-import org.jamienicol.episodes.db.ShowsTable;
-import org.jamienicol.episodes.services.RefreshShowService;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentPagerAdapter;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+import androidx.swiperefreshlayout.widget.CircularProgressDrawable;
+import androidx.viewpager.widget.ViewPager;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.google.android.material.tabs.TabLayout;
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
+import com.redcoracle.episodes.db.ShowsTable;
+import com.redcoracle.episodes.services.AsyncTask;
+import com.redcoracle.episodes.services.DeleteShowTask;
+import com.redcoracle.episodes.services.RefreshShowTask;
public class ShowActivity
- extends ActionBarActivity
+ extends AppCompatActivity
implements LoaderManager.LoaderCallbacks,
ViewPager.OnPageChangeListener,
SeasonsListFragment.OnSeasonSelectedListener
@@ -61,6 +66,7 @@ public class ShowActivity
private int showId;
private boolean isShowStarred;
+ private boolean isShowArchived;
private ImageView headerImage;
private Toolbar toolbar;
@@ -85,28 +91,25 @@ public void onCreate(Bundle savedInstanceState)
loaderArgs.putInt("showId", showId);
getSupportLoaderManager().initLoader(0, loaderArgs, this);
- headerImage = (ImageView)findViewById(R.id.header_image);
+ headerImage = findViewById(R.id.header_image);
- toolbar = (Toolbar)findViewById(R.id.toolbar);
+ toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- titleView = (TextView)findViewById(R.id.title);
-
- pagerAdapter =
- new PagerAdapter(this, getSupportFragmentManager(), showId);
+ titleView = findViewById(R.id.title);
- pager = (ViewPager)findViewById(R.id.pager);
+ pagerAdapter = new PagerAdapter(this, getSupportFragmentManager(), showId);
+ pager = findViewById(R.id.pager);
pager.setAdapter(pagerAdapter);
- pager.setOnPageChangeListener(this);
+ pager.addOnPageChangeListener(this);
- tabStrip = (TabLayout)findViewById(R.id.tab_strip);
+ tabStrip = findViewById(R.id.tab_strip);
tabStrip.setTabTextColors(getResources().getColorStateList(R.color.tab_text));
tabStrip.setupWithViewPager(pager);
// Set the default tab from preferences.
- final SharedPreferences prefs =
- PreferenceManager.getDefaultSharedPreferences(this);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
pager.setCurrentItem(prefs.getInt(KEY_DEFAULT_TAB, 0));
}
@@ -131,6 +134,15 @@ public boolean onPrepareOptionsMenu(Menu menu) {
toggleStarred.setTitle(R.string.menu_star_show);
}
+ final MenuItem toggleArchived = menu.findItem(R.id.menu_toggle_show_archived);
+ if (isShowArchived) {
+ toggleArchived.setIcon(R.drawable.ic_show_archived);
+ toggleArchived.setTitle(R.string.menu_unarchive_show);
+ } else {
+ toggleArchived.setIcon(R.drawable.ic_show_unarchived);
+ toggleArchived.setTitle(R.string.menu_archive_show);
+ }
+
return super.onPrepareOptionsMenu(menu);
}
@@ -145,6 +157,10 @@ public boolean onOptionsItemSelected(MenuItem item) {
toggleShowStarred();
return true;
+ case R.id.menu_toggle_show_archived:
+ toggleShowArchived();
+ return true;
+
case R.id.menu_refresh_show:
refreshShow();
return true;
@@ -176,7 +192,9 @@ public Loader onCreateLoader(int id, Bundle args) {
final String[] projection = {
ShowsTable.COLUMN_NAME,
ShowsTable.COLUMN_STARRED,
- ShowsTable.COLUMN_FANART_PATH
+ ShowsTable.COLUMN_ARCHIVED,
+ ShowsTable.COLUMN_FANART_PATH,
+ ShowsTable.COLUMN_POSTER_PATH
};
return new CursorLoader(this,
uri,
@@ -199,28 +217,36 @@ public void onLoadFinished(Loader loader, Cursor data) {
final int starredColumnIndex =
data.getColumnIndexOrThrow(ShowsTable.COLUMN_STARRED);
final boolean starred =
- data.getInt(starredColumnIndex) > 0 ? true : false;
+ data.getInt(starredColumnIndex) > 0;
if (isShowStarred != starred) {
isShowStarred = starred;
// toggle starred menu item needs updated
supportInvalidateOptionsMenu();
}
- final int fanartPathColumnIndex =
- data.getColumnIndexOrThrow(ShowsTable.COLUMN_FANART_PATH);
- final String fanartPath = data.getString(fanartPathColumnIndex);
- if (fanartPath != null && !fanartPath.equals("")) {
- final String fanartUrl =
- String.format("http://thetvdb.com/banners/%s", fanartPath);
-
- final DisplayImageOptions options =
- new DisplayImageOptions.Builder()
- .cacheInMemory(true)
- .cacheOnDisk(true)
- .build();
- ImageLoader.getInstance().displayImage(fanartUrl,
- headerImage,
- options);
+ // maybe update the state of the toggle archived menu item
+ final int archivedColumnIndex = data.getColumnIndexOrThrow(ShowsTable.COLUMN_ARCHIVED);
+ final boolean archived = data.getInt(archivedColumnIndex) > 0;
+ if (isShowArchived != archived) {
+ isShowArchived = archived;
+ // toggle archived menu item needs updated
+ supportInvalidateOptionsMenu();
+ }
+
+ final int posterPathColumnIndex = data.getColumnIndexOrThrow(ShowsTable.COLUMN_POSTER_PATH);
+ final String posterPath = data.getString(posterPathColumnIndex);
+ if (posterPath != null) {
+ CircularProgressDrawable placeholder = new CircularProgressDrawable(this);
+ placeholder.setColorFilter(ContextCompat.getColor(this, R.color.accent), PorterDuff.Mode.SRC_IN);
+ placeholder.setStrokeWidth(5f);
+ placeholder.setCenterRadius(60f);
+ placeholder.start();
+ final String artUrl = String.format("https://image.tmdb.org/t/p/w1280/%s", posterPath);
+ Glide.with(this)
+ .load(artUrl)
+ .placeholder(placeholder)
+ .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
+ .into(headerImage);
}
}
}
@@ -278,11 +304,27 @@ private void toggleShowStarred() {
selectionArgs);
}
- private void refreshShow() {
- final Intent intent = new Intent(this, RefreshShowService.class);
- intent.putExtra("showId", showId);
+ private void toggleShowArchived() {
+ final ContentResolver contentResolver = getContentResolver();
+ final AsyncQueryHandler handler = new AsyncQueryHandler(contentResolver) {};
+ final ContentValues values = new ContentValues();
+ values.put(ShowsTable.COLUMN_ARCHIVED, !isShowArchived);
+ final String selection = String.format("%s=?", ShowsTable.COLUMN_ID);
+ final String[] selectionArgs = {
+ String.valueOf(showId)
+ };
+
+ handler.startUpdate(0,
+ null,
+ ShowsProvider.CONTENT_URI_SHOWS,
+ values,
+ selection,
+ selectionArgs);
+ }
- startService(intent);
+
+ private void refreshShow() {
+ new AsyncTask().executeAsync(new RefreshShowTask(this.showId));
}
private void markShowWatched(boolean watched) {
@@ -309,32 +351,7 @@ private void markShowWatched(boolean watched) {
}
private void deleteShow() {
- final ContentResolver contentResolver = getContentResolver();
- final AsyncQueryHandler handler =
- new AsyncQueryHandler(contentResolver) {};
-
- /* delete all the show's episodes */
- final String epSelection =
- String.format("%s=?", EpisodesTable.COLUMN_SHOW_ID);
- final String[] epSelectionArgs = {
- String.valueOf(showId)
- };
-
- handler.startDelete(0,
- null,
- ShowsProvider.CONTENT_URI_EPISODES,
- epSelection,
- epSelectionArgs);
-
- /* delete the show itself */
- final Uri showUri =
- Uri.withAppendedPath(ShowsProvider.CONTENT_URI_SHOWS,
- String.valueOf(showId));
- handler.startDelete(0,
- null,
- showUri,
- null,
- null);
+ new AsyncTask().executeAsync(new DeleteShowTask(this.showId));
}
private static class PagerAdapter
@@ -354,7 +371,7 @@ public PagerAdapter(final Context context,
@Override
public int getCount() {
- return 4;
+ return 3;
}
@Override
@@ -366,8 +383,6 @@ public CharSequence getPageTitle(final int position) {
return context.getString(R.string.show_tab_episodes);
case 2:
return context.getString(R.string.show_tab_next);
- case 3:
- return context.getString(R.string.show_tab_notes);
default:
return null;
}
@@ -382,8 +397,6 @@ public Fragment getItem(final int position) {
return SeasonsListFragment.newInstance(showId);
case 2:
return NextEpisodeFragment.newInstance(showId);
- case 3:
- return ShowNotesFragment.newInstance(showId);
default:
return null;
}
diff --git a/app/src/main/java/org/jamienicol/episodes/ShowDetailsFragment.java b/app/src/main/java/com/redcoracle/episodes/ShowDetailsFragment.java
similarity index 92%
rename from app/src/main/java/org/jamienicol/episodes/ShowDetailsFragment.java
rename to app/src/main/java/com/redcoracle/episodes/ShowDetailsFragment.java
index 1bac285d..c52998ff 100644
--- a/app/src/main/java/org/jamienicol/episodes/ShowDetailsFragment.java
+++ b/app/src/main/java/com/redcoracle/episodes/ShowDetailsFragment.java
@@ -15,23 +15,26 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
+
+import androidx.fragment.app.Fragment;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+
+import com.redcoracle.episodes.db.ShowsProvider;
+import com.redcoracle.episodes.db.ShowsTable;
+
import java.text.DateFormat;
import java.util.Date;
-import org.jamienicol.episodes.db.ShowsProvider;
-import org.jamienicol.episodes.db.ShowsTable;
public class ShowDetailsFragment
extends Fragment
diff --git a/app/src/main/java/org/jamienicol/episodes/ShowNotesFragment.java b/app/src/main/java/com/redcoracle/episodes/ShowNotesFragment.java
similarity index 82%
rename from app/src/main/java/org/jamienicol/episodes/ShowNotesFragment.java
rename to app/src/main/java/com/redcoracle/episodes/ShowNotesFragment.java
index 9a986840..e5cdcef3 100644
--- a/app/src/main/java/org/jamienicol/episodes/ShowNotesFragment.java
+++ b/app/src/main/java/com/redcoracle/episodes/ShowNotesFragment.java
@@ -15,32 +15,23 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes;
+package com.redcoracle.episodes;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
-import android.text.Spanned;
import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
import android.widget.TextView;
-import org.jamienicol.episodes.db.ShowsProvider;
-import org.jamienicol.episodes.db.ShowsTable;
+import androidx.fragment.app.Fragment;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+
+import com.redcoracle.episodes.db.ShowsProvider;
+import com.redcoracle.episodes.db.ShowsTable;
public class ShowNotesFragment
extends Fragment
diff --git a/app/src/main/java/com/redcoracle/episodes/ShowsListFragment.java b/app/src/main/java/com/redcoracle/episodes/ShowsListFragment.java
new file mode 100644
index 00000000..853157b2
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/ShowsListFragment.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2012-2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.ListFragment;
+import androidx.loader.app.LoaderManager;
+import androidx.loader.content.CursorLoader;
+import androidx.loader.content.Loader;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.engine.DiskCacheStrategy;
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
+import com.redcoracle.episodes.db.ShowsTable;
+import com.redcoracle.episodes.services.AsyncTask;
+import com.redcoracle.episodes.services.RefreshAllShowsTask;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShowsListFragment
+ extends ListFragment
+ implements LoaderManager.LoaderCallbacks
+{
+ private static final int LOADER_ID_SHOWS = 0;
+ private static final int LOADER_ID_EPISODES = 1;
+
+ private static final String KEY_PREF_SHOWS_FILTER = "pref_shows_filter";
+ private static final String KEY_PREF_CONFIRMED_BACKUP = "pref_confirmed_backup";
+
+ private static final int SHOWS_FILTER_ALL = 0;
+ private static final int SHOWS_FILTER_STARRED = 1;
+ private static final int SHOWS_FILTER_UNCOMPLETED = 2;
+ private static final int SHOWS_FILTER_ARCHIVED = 3;
+ private static final int SHOWS_FILTER_UPCOMING = 4;
+
+ private ShowsListAdapter listAdapter;
+ private Cursor showsData;
+ private Cursor episodesData;
+
+ private ActivityResultLauncher reqNotificationPermission;
+
+
+ public interface OnShowSelectedListener {
+ public void onShowSelected(int showId);
+ }
+ private OnShowSelectedListener onShowSelectedListener;
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+
+ try {
+ onShowSelectedListener = (OnShowSelectedListener)activity;
+ } catch (ClassCastException e) {
+ final String message =
+ String.format("%s must implement OnShowSelectedListener",
+ activity.toString());
+ throw new ClassCastException(message);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ reqNotificationPermission = registerForActivityResult(
+ new ActivityResultContracts.RequestPermission(),
+ granted -> {}
+ );
+ }
+
+ public View onCreateView(LayoutInflater inflater,
+ ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.shows_list_fragment, container, false);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ listAdapter = new ShowsListAdapter(getActivity(),
+ null,
+ null);
+ setListAdapter(listAdapter);
+
+ getLoaderManager().initLoader(LOADER_ID_SHOWS, null, this);
+ getLoaderManager().initLoader(LOADER_ID_EPISODES, null, this);
+ }
+
+ @Override
+ public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
+ inflater.inflate(R.menu.shows_list_fragment, menu);
+ }
+
+ @Override
+ public void onPrepareOptionsMenu(Menu menu) {
+
+ // hide refresh all option if no shows exist
+ final boolean showsExist = (showsData != null && showsData.moveToFirst());
+ menu.findItem(R.id.menu_refresh_all_shows).setVisible(showsExist);
+
+ /* set the currently selected filter's menu item as checked */
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
+ final int filter = prefs.getInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_ALL);
+
+ switch (filter) {
+ case SHOWS_FILTER_ALL:
+ menu.findItem(R.id.menu_filter_all).setChecked(true);
+ break;
+ case SHOWS_FILTER_STARRED:
+ menu.findItem(R.id.menu_filter_starred).setChecked(true);
+ break;
+ case SHOWS_FILTER_UNCOMPLETED:
+ menu.findItem(R.id.menu_filter_uncompleted).setChecked(true);
+ break;
+ case SHOWS_FILTER_ARCHIVED:
+ menu.findItem(R.id.menu_filter_archived).setChecked(true);
+ break;
+ case SHOWS_FILTER_UPCOMING:
+ menu.findItem(R.id.menu_filter_upcoming).setChecked(true);
+ break;
+ }
+
+ super.onPrepareOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.menu_refresh_all_shows:
+ refreshAllShows();
+ return true;
+
+ case R.id.menu_filter_all:
+ case R.id.menu_filter_starred:
+ case R.id.menu_filter_uncompleted:
+ case R.id.menu_filter_archived:
+ case R.id.menu_filter_upcoming:
+ if (!item.isChecked()) {
+ item.setChecked(true);
+ }
+
+ final SharedPreferences prefs =
+ PreferenceManager.getDefaultSharedPreferences(getActivity());
+ final SharedPreferences.Editor editor = prefs.edit();
+ if (item.getItemId() == R.id.menu_filter_all) {
+ editor.putInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_ALL);
+ } else if (item.getItemId() == R.id.menu_filter_starred) {
+ editor.putInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_STARRED);
+ } else if (item.getItemId() == R.id.menu_filter_uncompleted) {
+ editor.putInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_UNCOMPLETED);
+ } else if (item.getItemId() == R.id.menu_filter_archived) {
+ editor.putInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_ARCHIVED);
+ } else if (item.getItemId() == R.id.menu_filter_upcoming) {
+ editor.putInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_UPCOMING);
+ }
+ editor.apply();
+
+ return true;
+
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ if (id == LOADER_ID_SHOWS) {
+ final String[] projection = {
+ ShowsTable.COLUMN_ID,
+ ShowsTable.COLUMN_NAME,
+ ShowsTable.COLUMN_STARRED,
+ ShowsTable.COLUMN_ARCHIVED,
+ ShowsTable.COLUMN_BANNER_PATH
+ };
+ return new CursorLoader(getActivity(),
+ ShowsProvider.CONTENT_URI_SHOWS,
+ projection,
+ null,
+ null,
+ ShowsTable.COLUMN_STARRED + " DESC, " +
+ ShowsTable.COLUMN_NAME + " COLLATE LOCALIZED ASC");
+
+ } else if (id == LOADER_ID_EPISODES) {
+ final String[] projection = {
+ EpisodesTable.COLUMN_SHOW_ID,
+ EpisodesTable.COLUMN_SEASON_NUMBER,
+ EpisodesTable.COLUMN_FIRST_AIRED,
+ EpisodesTable.COLUMN_WATCHED
+ };
+ final String selection =
+ String.format("%s!=?", EpisodesTable.COLUMN_SEASON_NUMBER);
+ final String[] selectionArgs = {
+ "0"
+ };
+ return new CursorLoader(getActivity(),
+ ShowsProvider.CONTENT_URI_EPISODES,
+ projection,
+ selection,
+ selectionArgs,
+ null);
+
+ } else {
+ throw new IllegalArgumentException("invalid loader id");
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, Cursor data) {
+ switch (loader.getId()) {
+ case LOADER_ID_SHOWS:
+ showsData = data;
+ listAdapter.swapShowsCursor(data);
+ break;
+
+ case LOADER_ID_EPISODES:
+ episodesData = data;
+ listAdapter.swapEpisodesCursor(data);
+ break;
+ }
+
+ getActivity().invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ onLoadFinished(loader, null);
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ onShowSelectedListener.onShowSelected((int)id);
+ }
+
+ private void checkNotificationPermission() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ return;
+ }
+
+ boolean granted = ContextCompat.checkSelfPermission(
+ this.requireContext(),
+ Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED;
+
+ if (!granted) {
+ reqNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS);
+ }
+ }
+
+ private void refreshAllShows() {
+ checkNotificationPermission();
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this.requireContext());
+ boolean confirmed_backup = prefs.getBoolean(KEY_PREF_CONFIRMED_BACKUP, false);
+
+ if (confirmed_backup) {
+ new AsyncTask().executeAsync(new RefreshAllShowsTask());
+ } else {
+ new AlertDialog.Builder(this.requireContext())
+ .setTitle(R.string.provider_change_warning_title)
+ .setMessage(R.string.provider_change_warning_detail)
+ .setPositiveButton(R.string.provider_change_warning_confirm, (dialogInterface, i) -> {
+ prefs.edit().putBoolean(KEY_PREF_CONFIRMED_BACKUP, true).apply();
+ new AsyncTask().executeAsync(new RefreshAllShowsTask());
+ })
+ .setNegativeButton(R.string.provider_change_warning_cancel, (dialogInterface, i) -> {})
+ .show();
+ }
+ }
+
+ private static class ShowsListAdapter
+ extends BaseAdapter
+ implements SharedPreferences.OnSharedPreferenceChangeListener
+ {
+ private Context context;
+ private Cursor showsCursor;
+ private int filter;
+ private EpisodesCounter episodesCounter;
+
+ // list of shows to be displayed with current filter. maps from
+ // the show's position in the list to its position in the cursor.
+ private List filteredShows;
+
+ public ShowsListAdapter(Context context,
+ Cursor showsCursor,
+ Cursor episodesCursor) {
+ this.context = context;
+
+ episodesCounter = new EpisodesCounter(EpisodesTable.COLUMN_SHOW_ID);
+ episodesCounter.swapCursor(episodesCursor);
+
+ final SharedPreferences prefs =
+ PreferenceManager.getDefaultSharedPreferences(context);
+ prefs.registerOnSharedPreferenceChangeListener(this);
+ filter = prefs.getInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_ALL);
+
+ filteredShows = new ArrayList();
+
+ swapShowsCursor(showsCursor);
+ }
+
+ public void swapShowsCursor(Cursor showsCursor) {
+ this.showsCursor = showsCursor;
+
+ updateFilter();
+ notifyDataSetChanged();
+ }
+
+ public void swapEpisodesCursor(Cursor episodesCursor) {
+ episodesCounter.swapCursor(episodesCursor);
+
+ if (showsCursor != null) {
+ updateFilter();
+ notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
+ String key) {
+ if (key.equals(KEY_PREF_SHOWS_FILTER)) {
+ filter = sharedPreferences.getInt(KEY_PREF_SHOWS_FILTER,
+ SHOWS_FILTER_ALL);
+
+ if (showsCursor != null) {
+ updateFilter();
+ notifyDataSetChanged();
+ }
+ }
+ }
+
+ private void updateFilter() {
+ filteredShows.clear();
+
+ if (showsCursor == null || !showsCursor.moveToFirst()) {
+ return;
+ }
+
+ do {
+ switch (filter) {
+ case SHOWS_FILTER_STARRED:
+ final int starredColumnIndex =
+ showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_STARRED);
+ if (showsCursor.getInt(starredColumnIndex) > 0) {
+ filteredShows.add(showsCursor.getPosition());
+ }
+ break;
+
+ case SHOWS_FILTER_ARCHIVED:
+ final int archivedColumnIndex = showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ARCHIVED);
+ if (showsCursor.getInt(archivedColumnIndex) > 0) {
+ filteredShows.add(showsCursor.getPosition());
+ }
+ break;
+
+ case SHOWS_FILTER_UNCOMPLETED:
+ final int idColumnIndexUncompleted = showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ID);
+ final int idUncompleted = showsCursor.getInt(idColumnIndexUncompleted);
+
+ if ((episodesCounter.getNumWatchedEpisodes(idUncompleted) < episodesCounter.getNumAiredEpisodes(idUncompleted)) &&
+ showsCursor.getInt(showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ARCHIVED)) == 0)
+ {
+ filteredShows.add(showsCursor.getPosition());
+ }
+ break;
+
+ case SHOWS_FILTER_UPCOMING:
+ final int idColumnIndexUpcoming = showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ID); //change to meaningful variable
+ final int idUpcoming = showsCursor.getInt(idColumnIndexUpcoming);
+
+ if((episodesCounter.getNumUpcomingEpisodes(idUpcoming) > 0) && (episodesCounter.getNumWatchedEpisodes(idUpcoming) == episodesCounter.getNumAiredEpisodes(idUpcoming)) &&
+ showsCursor.getInt(showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ARCHIVED)) == 0)
+ {
+ filteredShows.add(showsCursor.getPosition());
+ }
+ break;
+
+ default:
+ final int columnIndex = showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ARCHIVED);
+ if (showsCursor.getInt(columnIndex) == 0) {
+ filteredShows.add(showsCursor.getPosition());
+ }
+ break;
+ }
+ } while (showsCursor.moveToNext());
+ }
+
+ @Override
+ public int getCount() {
+ if (showsCursor == null) {
+ return 0;
+ } else {
+ return filteredShows.size();
+ }
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ showsCursor.moveToPosition(filteredShows.get(position));
+
+ final int idColumnIndex =
+ showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ID);
+ return showsCursor.getInt(idColumnIndex);
+ }
+
+ @Override
+ public View getView(int position,
+ View convertView,
+ ViewGroup parent) {
+
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ if(convertView == null) {
+ convertView = inflater.inflate(R.layout.shows_list_item,
+ parent,
+ false);
+ }
+
+ showsCursor.moveToPosition(filteredShows.get(position));
+
+ final int idColumnIndex = showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ID);
+ final int id = showsCursor.getInt(idColumnIndex);
+
+ final ContentResolver contentResolver = context.getContentResolver();
+
+ final TextView nameView = convertView.findViewById(R.id.show_name_view);
+ final int nameColumnIndex = showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_NAME);
+ final String name = showsCursor.getString(nameColumnIndex);
+ nameView.setText(name);
+
+ final ImageView bannerView = convertView.findViewById(R.id.banner_view);
+ final int bannerPathColumnIndex = showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_BANNER_PATH);
+ final String bannerPath = showsCursor.getString(bannerPathColumnIndex);
+
+ bannerView.setImageResource(R.drawable.blank_show_banner);
+ if (bannerPath != null && !bannerPath.equals("")) {
+ final String bannerUrl = String.format("https://image.tmdb.org/t/p/w1280/%s", bannerPath);
+
+ Glide.with(convertView)
+ .load(bannerUrl)
+ .diskCacheStrategy(DiskCacheStrategy.RESOURCE)
+ .centerCrop()
+ .placeholder(R.drawable.blank_show_banner)
+ .into(bannerView);
+ }
+
+ final ToggleButton starredToggle = (ToggleButton)convertView.findViewById(R.id.show_starred_toggle);
+ final int starredColumnIndex = showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_STARRED);
+ final boolean starred = showsCursor.getInt(starredColumnIndex) > 0;
+
+ starredToggle.setOnCheckedChangeListener(null);
+ starredToggle.setChecked(starred);
+
+ starredToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView,
+ boolean isChecked) {
+ final AsyncQueryHandler handler = new AsyncQueryHandler(contentResolver) {};
+ final ContentValues showValues = new ContentValues();
+ showValues.put(ShowsTable.COLUMN_STARRED, isChecked);
+
+ final Uri showUri = Uri.withAppendedPath(ShowsProvider.CONTENT_URI_SHOWS, String.valueOf(id));
+ handler.startUpdate(0,
+ null,
+ showUri,
+ showValues,
+ null,
+ null);
+ }
+ });
+
+ final ToggleButton archivedToggle = (ToggleButton)convertView.findViewById(R.id.show_archived_toggle);
+ final int archivedColumnIndex = showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ARCHIVED);
+ final boolean archived = showsCursor.getInt(archivedColumnIndex) > 0;
+
+ archivedToggle.setOnCheckedChangeListener(null);
+ archivedToggle.setChecked(archived);
+
+ archivedToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ final AsyncQueryHandler handler = new AsyncQueryHandler(contentResolver) {};
+ final ContentValues showValues = new ContentValues();
+ showValues.put(ShowsTable.COLUMN_ARCHIVED, isChecked);
+ final Uri showUri = Uri.withAppendedPath(ShowsProvider.CONTENT_URI_SHOWS, String.valueOf(id));
+ handler.startUpdate(0, null, showUri, showValues, null, null);
+ }
+ });
+
+ final int numAired = episodesCounter.getNumAiredEpisodes(id);
+ final int numWatched = episodesCounter.getNumWatchedEpisodes(id);
+ final int numUpcoming = episodesCounter.getNumUpcomingEpisodes(id);
+
+ final ProgressBar progressBar =
+ (ProgressBar)convertView.findViewById(R.id.show_progress_bar);
+ progressBar.setMax(numAired);
+ progressBar.setProgress(numWatched);
+
+ final TextView watchedCountView =
+ (TextView)convertView.findViewById(R.id.watched_count_view);
+ String watchedCountText = context.getString(R.string.watched_count,
+ numWatched,
+ numAired);
+ if (numUpcoming != 0) {
+ watchedCountText += " " +
+ context.getString(R.string.upcoming_count,
+ numUpcoming);
+ }
+ watchedCountView.setText(watchedCountText);
+
+ return convertView;
+ }
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/db/DatabaseOpenHelper.java b/app/src/main/java/com/redcoracle/episodes/db/DatabaseOpenHelper.java
new file mode 100644
index 00000000..d5984ca0
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/db/DatabaseOpenHelper.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012-2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes.db;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.util.Log;
+
+public class DatabaseOpenHelper extends SQLiteOpenHelper {
+ private static final String TAG = "DatabaseOpenHelper";
+ private static final String name = "episodes.db";
+ private static final int version = 9;
+
+ DatabaseOpenHelper(Context context) {
+ super(context, name, null, version);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ Log.d(TAG, "creating database");
+ ShowsTable.onCreate(db);
+ EpisodesTable.onCreate(db);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ Log.d(TAG, String.format("upgrading database from version %d to %d", oldVersion, newVersion));
+ ShowsTable.onUpgrade(db, oldVersion, newVersion);
+ EpisodesTable.onUpgrade(db, oldVersion, newVersion);
+ }
+
+ @Override
+ public void onOpen(SQLiteDatabase db) {
+ Log.d(TAG, "opening database.");
+ }
+
+ public static String getDbName() {
+ return name;
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/db/EpisodesTable.java b/app/src/main/java/com/redcoracle/episodes/db/EpisodesTable.java
new file mode 100644
index 00000000..8d6d8865
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/db/EpisodesTable.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2012-2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes.db;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+import java.util.Arrays;
+
+public class EpisodesTable {
+ private static final String TAG = "EpisodesTable";
+
+ static final String TABLE_NAME = "episodes";
+
+ public static final String COLUMN_ID = BaseColumns._ID;
+ public static final String COLUMN_TVDB_ID = "tvdb_id";
+ public static final String COLUMN_TMDB_ID = "tmdb_id";
+ public static final String COLUMN_IMDB_ID = "imdb_id";
+ public static final String COLUMN_SHOW_ID = "show_id";
+ public static final String COLUMN_NAME = "name";
+ public static final String COLUMN_LANGUAGE = "language";
+ public static final String COLUMN_OVERVIEW = "overview";
+ public static final String COLUMN_EPISODE_NUMBER = "episode_number";
+ public static final String COLUMN_SEASON_NUMBER = "season_number";
+ public static final String COLUMN_FIRST_AIRED = "first_aired";
+ public static final String COLUMN_WATCHED = "watched";
+
+ public static String createTableSQL(String table_name) {
+ return String.format(
+ "CREATE TABLE %s (" +
+ "%s INTEGER PRIMARY KEY," +
+ "%s INTEGER UNIQUE," +
+ "%s INTEGER UNIQUE," +
+ "%s TEXT UNIQUE," +
+ "%s INTEGER NOT NULL," +
+ "%s VARCHAR(200) NOT NULL," +
+ "%s TEXT," +
+ "%s TEXT," +
+ "%s INTEGER," +
+ "%s INTEGER," +
+ "%s DATE," +
+ "%s BOOLEAN" +
+ ");",
+ table_name,
+ COLUMN_ID,
+ COLUMN_TVDB_ID,
+ COLUMN_TMDB_ID,
+ COLUMN_IMDB_ID,
+ COLUMN_SHOW_ID,
+ COLUMN_NAME,
+ COLUMN_LANGUAGE,
+ COLUMN_OVERVIEW,
+ COLUMN_EPISODE_NUMBER,
+ COLUMN_SEASON_NUMBER,
+ COLUMN_FIRST_AIRED,
+ COLUMN_WATCHED
+ );
+ }
+
+ public static void onCreate(SQLiteDatabase db) {
+ String create = createTableSQL(TABLE_NAME);
+
+ Log.d(TAG, String.format("creating episodes table: %s", create));
+
+ db.execSQL(create);
+ }
+
+ static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion <= 7) {
+ // Add language column
+ Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null);
+ String[] columns = cursor.getColumnNames();
+ if (!Arrays.asList(columns).contains(COLUMN_LANGUAGE)) {
+ Log.d(TAG, "upgrading episodes table: adding language column");
+ db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s TEXT", TABLE_NAME, COLUMN_LANGUAGE));
+ }
+ cursor.close();
+ }
+
+ if (oldVersion <= 8) {
+ // Add TMDB/IMDB columns
+ db.beginTransaction();
+ try {
+ final String temp_table_name = String.format("new_%s", TABLE_NAME);
+ String create_table = createTableSQL(temp_table_name);
+ String insert_columns = String.format(
+ "%s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
+ COLUMN_ID, COLUMN_TVDB_ID, COLUMN_SHOW_ID, COLUMN_NAME, COLUMN_LANGUAGE,
+ COLUMN_OVERVIEW, COLUMN_EPISODE_NUMBER, COLUMN_SEASON_NUMBER,
+ COLUMN_FIRST_AIRED, COLUMN_WATCHED
+ );
+ db.execSQL(create_table);
+ db.execSQL(String.format(
+ "INSERT INTO %s (%s) SELECT %s FROM %s",
+ temp_table_name, insert_columns, insert_columns, TABLE_NAME
+ ));
+ db.execSQL(String.format("DROP TABLE %s", TABLE_NAME));
+ db.execSQL(String.format(
+ "ALTER TABLE %s RENAME TO %s",
+ temp_table_name, TABLE_NAME
+ ));
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/db/ShowsProvider.java b/app/src/main/java/com/redcoracle/episodes/db/ShowsProvider.java
new file mode 100644
index 00000000..84968b12
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/db/ShowsProvider.java
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2012 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes.db;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteConstraintException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteStatement;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.redcoracle.episodes.BuildConfig;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+public class ShowsProvider extends ContentProvider {
+ private static final String TAG = "ShowsProvider";
+ public static final String URI_AUTHORITY = BuildConfig.APPLICATION_ID + ".db.ShowsProvider";
+
+ private static final Uri CONTENT_URI_BASE =
+ Uri.parse(ContentResolver.SCHEME_CONTENT + "://" + ShowsProvider.URI_AUTHORITY);
+
+ public static final Uri CONTENT_URI_SHOWS =
+ Uri.parse(ContentResolver.SCHEME_CONTENT
+ + "://" + ShowsProvider.URI_AUTHORITY +
+ "/" + ShowsTable.TABLE_NAME
+ );
+
+ public static final Uri CONTENT_URI_EPISODES =
+ Uri.parse(ContentResolver.SCHEME_CONTENT +
+ "://" + ShowsProvider.URI_AUTHORITY +
+ "/" + EpisodesTable.TABLE_NAME
+ );
+
+ public static final String CONTENT_TYPE_SHOW_DIR = ContentResolver.CURSOR_DIR_BASE_TYPE + "/show";
+ public static final String CONTENT_TYPE_SHOW_ITEM = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/show";
+ public static final String CONTENT_TYPE_EPISODE_DIR = ContentResolver.CURSOR_DIR_BASE_TYPE + "/episode";
+ public static final String CONTENT_TYPE_EPISODE_ITEM = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/episode";
+
+ private static final int URI_TYPE_SHOWS = 1;
+ private static final int URI_TYPE_SHOWS_ID = 2;
+ private static final int URI_TYPE_EPISODES = 3;
+ private static final int URI_TYPE_EPISODES_ID = 4;
+
+ private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
+ static {
+ uriMatcher.addURI(URI_AUTHORITY, ShowsTable.TABLE_NAME, URI_TYPE_SHOWS);
+ uriMatcher.addURI(URI_AUTHORITY, ShowsTable.TABLE_NAME + "/#", URI_TYPE_SHOWS_ID);
+ uriMatcher.addURI(URI_AUTHORITY, EpisodesTable.TABLE_NAME, URI_TYPE_EPISODES);
+ uriMatcher.addURI(URI_AUTHORITY, EpisodesTable.TABLE_NAME + "/#", URI_TYPE_EPISODES_ID);
+ }
+
+ private DatabaseOpenHelper databaseOpenHelper;
+
+ @Override
+ public Cursor query(@NonNull Uri uri,
+ String[] projection,
+ String selection,
+ String[] selectionArgs,
+ String sortOrder) {
+ String table;
+ String sel;
+
+ switch (uriMatcher.match(uri)) {
+ case URI_TYPE_SHOWS:
+ table = ShowsTable.TABLE_NAME;
+ sel = selection;
+ break;
+
+ case URI_TYPE_SHOWS_ID:
+ table = ShowsTable.TABLE_NAME;
+ sel = String.format("%s=%s", ShowsTable.COLUMN_ID, uri.getLastPathSegment());
+ if (selection != null) {
+ sel += " AND " + selection;
+ }
+ break;
+
+ case URI_TYPE_EPISODES:
+ table = EpisodesTable.TABLE_NAME;
+ sel = selection;
+ break;
+
+ case URI_TYPE_EPISODES_ID:
+ table = EpisodesTable.TABLE_NAME;
+ sel = String.format("%s=%s", EpisodesTable.COLUMN_ID, uri.getLastPathSegment());
+ if (selection != null) {
+ sel += " AND " + selection;
+ }
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+
+ SQLiteDatabase db = databaseOpenHelper.getReadableDatabase();
+ Cursor cursor = db.query(table, projection, sel, selectionArgs, null, null, sortOrder);
+ cursor.setNotificationUri(getContext().getContentResolver(), uri);
+
+ return cursor;
+ }
+
+ @Override
+ public Uri insert(@NonNull Uri uri, ContentValues values) {
+ String table;
+ Uri contentUri;
+ if (uriMatcher.match(uri) == URI_TYPE_SHOWS) {
+ table = ShowsTable.TABLE_NAME;
+ contentUri = CONTENT_URI_SHOWS;
+ } else if (uriMatcher.match(uri) == URI_TYPE_EPISODES) {
+ table = EpisodesTable.TABLE_NAME;
+ contentUri = CONTENT_URI_EPISODES;
+ } else {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+
+ SQLiteDatabase db = databaseOpenHelper.getWritableDatabase();
+ try {
+ long rowId = db.insertOrThrow(table, null, values);
+ Log.i(TAG, String.format("succesfully inserted row. id: %d", rowId));
+ Uri rowUri = ContentUris.withAppendedId(contentUri, rowId);
+ getContext().getContentResolver().notifyChange(rowUri, null);
+ return rowUri;
+ } catch (SQLiteConstraintException e) {
+ Log.i(TAG, String.format("constraint error inserting row: %s", e.toString()));
+ return null;
+ }
+ }
+
+ @Override
+ public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
+ String table;
+ String sel;
+
+ switch (uriMatcher.match(uri)) {
+ case URI_TYPE_SHOWS:
+ table = ShowsTable.TABLE_NAME;
+ sel = selection;
+ break;
+
+ case URI_TYPE_SHOWS_ID:
+ table = ShowsTable.TABLE_NAME;
+ sel = String.format("%s=%s", ShowsTable.COLUMN_ID, uri.getLastPathSegment());
+ if (selection != null) {
+ sel += " AND " + selection;
+ }
+ break;
+
+ case URI_TYPE_EPISODES:
+ table = EpisodesTable.TABLE_NAME;
+ sel = selection;
+ break;
+
+ case URI_TYPE_EPISODES_ID:
+ table = EpisodesTable.TABLE_NAME;
+ sel = String.format("%s=%s", EpisodesTable.COLUMN_ID, uri.getLastPathSegment());
+ if (selection != null) {
+ sel += " AND " + selection;
+ }
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+
+ SQLiteDatabase db = databaseOpenHelper.getWritableDatabase();
+ int count = db.delete(table, sel, selectionArgs);
+
+ getContext().getContentResolver().notifyChange(uri, null);
+
+ return count;
+ }
+
+ @Override
+ public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ String table;
+ String sel;
+
+ switch (uriMatcher.match(uri)) {
+ case URI_TYPE_SHOWS:
+ table = ShowsTable.TABLE_NAME;
+ sel = selection;
+ break;
+
+ case URI_TYPE_SHOWS_ID:
+ table = ShowsTable.TABLE_NAME;
+ sel = String.format("%s=%s", ShowsTable.COLUMN_ID, uri.getLastPathSegment());
+ if (selection != null) {
+ sel += " AND " + selection;
+ }
+ break;
+
+ case URI_TYPE_EPISODES:
+ table = EpisodesTable.TABLE_NAME;
+ sel = selection;
+ break;
+
+ case URI_TYPE_EPISODES_ID:
+ table = EpisodesTable.TABLE_NAME;
+ sel = String.format("%s=%s", EpisodesTable.COLUMN_ID, uri.getLastPathSegment());
+ if (selection != null) {
+ sel += " AND " + selection;
+ }
+ break;
+
+ default:
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+
+ SQLiteDatabase db = databaseOpenHelper.getWritableDatabase();
+ int count = db.update(table, values, sel, selectionArgs);
+
+ getContext().getContentResolver().notifyChange(uri, null);
+
+ return count;
+ }
+
+ @Override
+ public int bulkInsert(Uri uri, ContentValues[] values) {
+ // Updating episodes is by far the most time consuming operation so this
+ // function only optimises that. Moving to an ORM like Room would be ideal,
+ // but for now this improved syncing a library with ~500 shows/26000 episodes
+ // from 40+ minutes to less than 10.
+ final SQLiteDatabase db = databaseOpenHelper.getWritableDatabase();
+ int inserted;
+ if (uriMatcher.match(uri) == URI_TYPE_EPISODES) {
+ db.beginTransaction();
+ try {
+ for (ContentValues v : values) {
+ StringBuilder updateBinds = new StringBuilder();
+ ArrayList bindKeys = new ArrayList<>();
+ for (Iterator iterator = v.keySet().iterator(); iterator.hasNext(); ) {
+ String key = iterator.next();
+ String bindArg = v.getAsString(key);
+ // Filter out nulls
+ if (bindArg != null && !key.equals("_id")) {
+ updateBinds.append(String.format("%s=?", key));
+ bindKeys.add(key);
+ if (iterator.hasNext()) {
+ updateBinds.append(",");
+ }
+ }
+ }
+
+ SQLiteStatement insert = db.compileStatement(String.format(
+ "UPDATE %s SET %s WHERE _id=%s",
+ EpisodesTable.TABLE_NAME,
+ updateBinds,
+ v.getAsString("_id")
+ ));
+ for (int i = 0; i < bindKeys.size(); i++) {
+ insert.bindString(i + 1, v.getAsString(bindKeys.get(i)));
+ }
+ insert.execute();
+ }
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ inserted = values.length;
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown URI " + uri);
+ }
+ getContext().getContentResolver().notifyChange(uri, null);
+ return inserted;
+ }
+
+ @Override
+ public String getType(@NonNull Uri uri) {
+ switch (uriMatcher.match(uri)) {
+ case URI_TYPE_SHOWS:
+ return CONTENT_TYPE_SHOW_DIR;
+
+ case URI_TYPE_SHOWS_ID:
+ return CONTENT_TYPE_SHOW_ITEM;
+
+ case URI_TYPE_EPISODES:
+ return CONTENT_TYPE_EPISODE_DIR;
+
+ case URI_TYPE_EPISODES_ID:
+ return CONTENT_TYPE_EPISODE_ITEM;
+
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ databaseOpenHelper = new DatabaseOpenHelper(getContext());
+ return true;
+ }
+
+ public static void reloadDatabase(Context context) {
+ final ContentResolver resolver = context.getContentResolver();
+ final ContentProviderClient client = resolver.acquireContentProviderClient(URI_AUTHORITY);
+ final ShowsProvider provider = (ShowsProvider)client.getLocalContentProvider();
+
+ provider.databaseOpenHelper.close();
+ provider.databaseOpenHelper = new DatabaseOpenHelper(provider.getContext());
+
+ resolver.notifyChange(CONTENT_URI_BASE, null);
+
+ client.release();
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/db/ShowsTable.java b/app/src/main/java/com/redcoracle/episodes/db/ShowsTable.java
new file mode 100644
index 00000000..b0dae6da
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/db/ShowsTable.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2012-2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes.db;
+
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.provider.BaseColumns;
+import android.util.Log;
+
+public class ShowsTable {
+ private static final String TAG = "ShowsTable";
+
+ public static final String TABLE_NAME = "shows";
+
+ public static final String COLUMN_ID = BaseColumns._ID;
+ public static final String COLUMN_TVDB_ID = "tvdb_id";
+ public static final String COLUMN_TMDB_ID = "tmdb_id";
+ public static final String COLUMN_IMDB_ID = "imdb_id";
+ public static final String COLUMN_NAME = "name";
+ public static final String COLUMN_LANGUAGE = "language";
+ public static final String COLUMN_OVERVIEW = "overview";
+ public static final String COLUMN_FIRST_AIRED = "first_aired";
+ public static final String COLUMN_STARRED = "starred";
+ public static final String COLUMN_ARCHIVED = "archived";
+ public static final String COLUMN_BANNER_PATH = "banner_path";
+ public static final String COLUMN_FANART_PATH = "fanart_path";
+ public static final String COLUMN_POSTER_PATH = "poster_path";
+ public static final String COLUMN_NOTES = "notes";
+
+ public static final String COLUMN_TYPE_ID = "INTEGER PRIMARY KEY";
+ public static final String COLUMN_TYPE_TVDB_ID = "INTEGER UNIQUE";
+ public static final String COLUMN_TYPE_TMDB_ID = "INTEGER UNIQUE";
+ public static final String COLUMN_TYPE_IMDB_ID = "STRING UNIQUE";
+ public static final String COLUMN_TYPE_NAME = "TEXT NOT NULL";
+ public static final String COLUMN_TYPE_LANGUAGE = "TEXT";
+ public static final String COLUMN_TYPE_OVERVIEW = "TEXT";
+ public static final String COLUMN_TYPE_FIRST_AIRED = "DATE";
+ public static final String COLUMN_TYPE_STARRED = "BOOLEAN DEFAULT 0";
+ public static final String COLUMN_TYPE_ARCHIVED = "BOOLEAN DEFAULT 0";
+ public static final String COLUMN_TYPE_BANNER_PATH = "TEXT";
+ public static final String COLUMN_TYPE_FANART_PATH = "TEXT";
+ public static final String COLUMN_TYPE_POSTER_PATH = "TEXT";
+ public static final String COLUMN_TYPE_NOTES = "TEXT";
+
+ public static String createTableSQL(String table_name) {
+ return String.format(
+ "CREATE TABLE %s (" +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s," +
+ " %s %s" +
+ ");",
+ table_name,
+ COLUMN_ID, COLUMN_TYPE_ID,
+ COLUMN_TVDB_ID, COLUMN_TYPE_TVDB_ID,
+ COLUMN_TMDB_ID, COLUMN_TYPE_TMDB_ID,
+ COLUMN_IMDB_ID, COLUMN_TYPE_IMDB_ID,
+ COLUMN_NAME, COLUMN_TYPE_NAME,
+ COLUMN_LANGUAGE, COLUMN_TYPE_LANGUAGE,
+ COLUMN_OVERVIEW, COLUMN_TYPE_OVERVIEW,
+ COLUMN_FIRST_AIRED, COLUMN_TYPE_FIRST_AIRED,
+ COLUMN_STARRED, COLUMN_TYPE_STARRED,
+ COLUMN_ARCHIVED, COLUMN_TYPE_ARCHIVED,
+ COLUMN_BANNER_PATH, COLUMN_TYPE_BANNER_PATH,
+ COLUMN_FANART_PATH, COLUMN_TYPE_FANART_PATH,
+ COLUMN_POSTER_PATH, COLUMN_TYPE_POSTER_PATH,
+ COLUMN_NOTES, COLUMN_TYPE_NOTES
+ );
+ }
+
+ public static void onCreate(SQLiteDatabase db) {
+ String create = createTableSQL(TABLE_NAME);
+ Log.d(TAG, String.format("creating shows table: %s", create));
+ db.execSQL(create);
+ }
+
+ public static void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ if (oldVersion < 2) {
+ // Add starred column
+ Log.d(TAG, "upgrading shows table: adding starred column");
+ db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
+ TABLE_NAME,
+ COLUMN_STARRED,
+ COLUMN_TYPE_STARRED));
+ }
+
+ if (oldVersion < 3) {
+ // Add banner path column
+ Log.d(TAG, "upgrading shows table: adding banner path column");
+ db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
+ TABLE_NAME,
+ COLUMN_BANNER_PATH,
+ COLUMN_TYPE_BANNER_PATH));
+ }
+
+ if (oldVersion < 4) {
+ // Add fanart path and poster path columns
+ Log.d(TAG, "upgrading shows table: adding fanart path column");
+ db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
+ TABLE_NAME,
+ COLUMN_FANART_PATH,
+ COLUMN_TYPE_FANART_PATH));
+
+ Log.d(TAG, "upgrading shows table: adding poster path column");
+ db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
+ TABLE_NAME,
+ COLUMN_POSTER_PATH,
+ COLUMN_TYPE_POSTER_PATH));
+ }
+
+ if (oldVersion < 5) {
+ // Add notes column
+ Log.d(TAG, "upgrading shows table: adding notes column");
+ db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
+ TABLE_NAME,
+ COLUMN_NOTES,
+ COLUMN_TYPE_NOTES));
+ }
+
+ if (oldVersion < 6) {
+ // Add language column
+ Log.d(TAG, "upgrading shows table: adding language column");
+ db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
+ TABLE_NAME,
+ COLUMN_LANGUAGE,
+ COLUMN_TYPE_LANGUAGE));
+ }
+
+ if (oldVersion < 7) {
+ // Add archived column
+ Log.d(TAG, "upgrading shows table: adding archived column");
+ db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
+ TABLE_NAME,
+ COLUMN_ARCHIVED,
+ COLUMN_TYPE_ARCHIVED));
+ }
+
+ if (oldVersion < 9) {
+ // Add TMDB/IMDB columns
+ db.beginTransaction();
+ try {
+ final String temp_table_name = String.format("new_%s", TABLE_NAME);
+
+ String create_table = createTableSQL(temp_table_name);
+ String insert_columns = String.format(
+ "%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s",
+ COLUMN_ID, COLUMN_TVDB_ID, COLUMN_NAME, COLUMN_LANGUAGE, COLUMN_OVERVIEW,
+ COLUMN_FIRST_AIRED, COLUMN_STARRED, COLUMN_ARCHIVED, COLUMN_BANNER_PATH,
+ COLUMN_FANART_PATH, COLUMN_POSTER_PATH, COLUMN_NOTES
+ );
+
+ db.execSQL(create_table);
+ db.execSQL(String.format(
+ "INSERT INTO %s (%s) SELECT %s FROM %s",
+ temp_table_name, insert_columns, insert_columns, TABLE_NAME
+ ));
+
+ db.execSQL(String.format("DROP TABLE %s", TABLE_NAME));
+ db.execSQL(String.format(
+ "ALTER TABLE %s RENAME TO %s",
+ temp_table_name, TABLE_NAME
+ ));
+
+ final Cursor fk_query = db.rawQuery("PRAGMA foreign_key_check", null);
+ final int fk_check = fk_query.getCount();
+ fk_query.close();
+ if (fk_check == 0) {
+ db.setTransactionSuccessful();
+ }
+ } finally {
+ db.endTransaction();
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/services/AddShowTask.java b/app/src/main/java/com/redcoracle/episodes/services/AddShowTask.java
new file mode 100644
index 00000000..e8e27d89
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/services/AddShowTask.java
@@ -0,0 +1,135 @@
+package com.redcoracle.episodes.services;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.redcoracle.episodes.EpisodesApplication;
+import com.redcoracle.episodes.R;
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
+import com.redcoracle.episodes.db.ShowsTable;
+import com.redcoracle.episodes.tvdb.Client;
+import com.redcoracle.episodes.tvdb.Episode;
+import com.redcoracle.episodes.tvdb.Show;
+
+import java.util.LinkedList;
+import java.util.concurrent.Callable;
+
+public class AddShowTask implements Callable {
+ private static final String TAG = "AddShowTask";
+ private final int tmdbId;
+ private final String showName;
+ private final String showLanguage;
+ private final Context context;
+
+ public AddShowTask(int tmdbId, String showName, String showLanguage) {
+ this.tmdbId = tmdbId;
+ this.showName = showName;
+ this.showLanguage = showLanguage;
+ this.context = EpisodesApplication.getInstance().getApplicationContext();
+ }
+
+ @Override
+ public Void call() {
+ final Client tmdbClient = new Client();
+ Show show = tmdbClient.getShow(this.tmdbId, this.showLanguage, false);
+
+ if (!checkAlreadyAdded(show)) {
+ this.showMessage(this.context.getString(R.string.adding_show, showName));
+ show = tmdbClient.getShow(this.tmdbId, this.showLanguage, true);
+ final int showId = insertShow(show);
+ this.insertEpisodes(show.getEpisodes().toArray(new Episode[0]), showId);
+ showMessage(this.context.getString(R.string.show_added, showName));
+ } else {
+ showMessage(this.context.getString(R.string.show_already_added, showName));
+ }
+ return null;
+ }
+
+ private boolean checkAlreadyAdded(Show show) {
+ final String[] projection = {};
+ String selection = String.format("%s=?", ShowsTable.COLUMN_TMDB_ID);
+ LinkedList selectionArgs = new LinkedList<>();
+ selectionArgs.add(Integer.valueOf(show.getTmdbId()).toString());
+
+ if (show.getTvdbId() > 0) {
+ selection += String.format(" OR %s=?", ShowsTable.COLUMN_TVDB_ID);
+ selectionArgs.add(Integer.valueOf(show.getTvdbId()).toString());
+ }
+ if (show.getImdbId() != null && !show.getImdbId().equals("")) {
+ selection += String.format(" OR %s=?", ShowsTable.COLUMN_IMDB_ID);
+ selectionArgs.add(show.getImdbId());
+ }
+ final ContentResolver resolver = this.context.getContentResolver();
+ final Cursor cursor = resolver.query(
+ ShowsProvider.CONTENT_URI_SHOWS,
+ projection,
+ selection,
+ selectionArgs.toArray(new String[0]),
+ null
+ );
+ final boolean existing = cursor.moveToFirst();
+ cursor.close();
+ return existing;
+ }
+
+ private int insertShow(Show show){
+ final ContentValues showValues = new ContentValues();
+ if (show.getTvdbId() != 0) {
+ showValues.put(ShowsTable.COLUMN_TVDB_ID, show.getTvdbId());
+ }
+ showValues.put(ShowsTable.COLUMN_TMDB_ID, show.getTmdbId());
+ showValues.put(ShowsTable.COLUMN_IMDB_ID, show.getImdbId());
+ showValues.put(ShowsTable.COLUMN_NAME, show.getName());
+ showValues.put(ShowsTable.COLUMN_LANGUAGE, show.getLanguage());
+ showValues.put(ShowsTable.COLUMN_OVERVIEW, show.getOverview());
+ if (show.getFirstAired() != null) {
+ showValues.put(ShowsTable.COLUMN_FIRST_AIRED, show.getFirstAired().getTime() / 1000);
+ }
+ showValues.put(ShowsTable.COLUMN_BANNER_PATH, show.getBannerPath());
+ showValues.put(ShowsTable.COLUMN_FANART_PATH, show.getFanartPath());
+ showValues.put(ShowsTable.COLUMN_POSTER_PATH, show.getPosterPath());
+
+ final Uri showUri = this.context.getContentResolver().insert(ShowsProvider.CONTENT_URI_SHOWS, showValues);
+ final int showId = Integer.parseInt(showUri.getLastPathSegment());
+ Log.i(TAG, String.format("show %s successfully added to database as row %d. adding episodes", show.getName(), showId));
+ return showId;
+ }
+
+ private void insertEpisodes(Episode[] episodes, int showId) {
+ final ContentValues[] values = new ContentValues[episodes.length];
+
+ for (int i = 0; i < episodes.length; i++) {
+ ContentValues value = new ContentValues();
+ value.put(EpisodesTable.COLUMN_TVDB_ID, episodes[i].getTvdbId());
+ value.put(EpisodesTable.COLUMN_TMDB_ID, episodes[i].getTmdbId());
+ value.put(EpisodesTable.COLUMN_IMDB_ID, episodes[i].getImdbId());
+ value.put(EpisodesTable.COLUMN_SHOW_ID, showId);
+ value.put(EpisodesTable.COLUMN_NAME, episodes[i].getName());
+ value.put(EpisodesTable.COLUMN_LANGUAGE, episodes[i].getLanguage());
+ value.put(EpisodesTable.COLUMN_OVERVIEW, episodes[i].getOverview());
+ value.put(EpisodesTable.COLUMN_EPISODE_NUMBER, episodes[i].getEpisodeNumber());
+ value.put(EpisodesTable.COLUMN_SEASON_NUMBER, episodes[i].getSeasonNumber());
+ if (episodes[i].getFirstAired() != null) {
+ value.put(EpisodesTable.COLUMN_FIRST_AIRED, episodes[i].getFirstAired().getTime() / 1000);
+ }
+ values[i] = value;
+ }
+
+ for (ContentValues value : values) {
+ this.context.getContentResolver().insert(ShowsProvider.CONTENT_URI_EPISODES, value);
+ }
+ }
+
+ private void showMessage(String message) {
+ Handler handler = new Handler(Looper.getMainLooper());
+ handler.post(() -> Toast.makeText(context, message, Toast.LENGTH_SHORT).show());
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/services/AsyncTask.java b/app/src/main/java/com/redcoracle/episodes/services/AsyncTask.java
new file mode 100644
index 00000000..ca898bbb
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/services/AsyncTask.java
@@ -0,0 +1,19 @@
+package com.redcoracle.episodes.services;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class AsyncTask {
+ private final Executor executor = Executors.newSingleThreadExecutor();
+
+ public void executeAsync(Callable callable) {
+ executor.execute(() -> {
+ try {
+ callable.call();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/services/BackupTask.java b/app/src/main/java/com/redcoracle/episodes/services/BackupTask.java
new file mode 100644
index 00000000..d31b379f
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/services/BackupTask.java
@@ -0,0 +1,68 @@
+package com.redcoracle.episodes.services;
+
+import android.content.Context;
+import android.os.Environment;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+
+import com.redcoracle.episodes.EpisodesApplication;
+import com.redcoracle.episodes.R;
+import com.redcoracle.episodes.db.DatabaseOpenHelper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.concurrent.Callable;
+
+public class BackupTask implements Callable {
+ private final static String TAG = BackupTask.class.getName();
+ private final Context context;
+ private final String destinationFileName;
+
+ public BackupTask(String destinationFileName) {
+ this.destinationFileName = destinationFileName;
+ this.context = EpisodesApplication.getInstance().getApplicationContext();
+ }
+
+ public Void call() {
+ Log.i(TAG, "Backing up library.");
+ if (!isExternalStorageReadable()) {
+ Log.i(TAG, "Storage is not readable.");
+ return null;
+ }
+ final File databaseFile = this.context.getDatabasePath(DatabaseOpenHelper.getDbName());
+ final File destinationDirectory = new File(this.context.getExternalFilesDir(null), "episodes");
+ if (!destinationDirectory.mkdirs()) {
+ Log.e(TAG, String.format("Error creating backup directory '%s'.", destinationDirectory.getPath()));
+ }
+ final File destinationFile = new File(destinationDirectory, this.destinationFileName);
+ try {
+ FileChannel src = new FileInputStream(databaseFile).getChannel();
+ FileChannel dest = new FileOutputStream(destinationFile).getChannel();
+ dest.transferFrom(src, 0, src.size());
+ ContextCompat.getMainExecutor(this.context).execute(() -> Toast.makeText(
+ this.context,
+ String.format(this.context.getString(R.string.back_up_success_message), this.destinationFileName),
+ Toast.LENGTH_LONG
+ ).show());
+ Log.i(TAG, String.format("Library backed up successfully: '%s'.", destinationFile.getPath()));
+ src.close();
+ dest.close();
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ private boolean isExternalStorageReadable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state);
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/services/DeleteShowTask.java b/app/src/main/java/com/redcoracle/episodes/services/DeleteShowTask.java
new file mode 100644
index 00000000..248e3d2d
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/services/DeleteShowTask.java
@@ -0,0 +1,34 @@
+package com.redcoracle.episodes.services;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.redcoracle.episodes.EpisodesApplication;
+import com.redcoracle.episodes.db.EpisodesTable;
+import com.redcoracle.episodes.db.ShowsProvider;
+
+import java.util.concurrent.Callable;
+
+public class DeleteShowTask implements Callable {
+ private static final String TAG = DeleteShowTask.class.getName();
+ private int showId;
+ private Context context;
+
+ public DeleteShowTask(int showId) {
+ this.showId = showId;
+ this.context = EpisodesApplication.getInstance().getApplicationContext();
+ }
+
+ @Override
+ public Void call() {
+ final ContentResolver resolver = this.context.getContentResolver();
+ final String selection = String.format("%s=?", EpisodesTable.COLUMN_SHOW_ID);
+ final String[] selectionArgs = {String.valueOf(this.showId)};
+ int episodes = resolver.delete(ShowsProvider.CONTENT_URI_EPISODES, selection, selectionArgs);
+ Log.d(TAG, String.format("Deleted %s episodes", episodes));
+ resolver.delete(Uri.withAppendedPath(ShowsProvider.CONTENT_URI_SHOWS, String.valueOf(showId)), null, null);
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/services/RefreshAllShowsTask.java b/app/src/main/java/com/redcoracle/episodes/services/RefreshAllShowsTask.java
new file mode 100644
index 00000000..4c17a21b
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/services/RefreshAllShowsTask.java
@@ -0,0 +1,63 @@
+package com.redcoracle.episodes.services;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationManagerCompat;
+
+import com.redcoracle.episodes.EpisodesApplication;
+import com.redcoracle.episodes.R;
+import com.redcoracle.episodes.db.ShowsProvider;
+import com.redcoracle.episodes.db.ShowsTable;
+
+import java.util.concurrent.Callable;
+
+import static com.redcoracle.episodes.RefreshShowUtil.refreshShow;
+
+public class RefreshAllShowsTask implements Callable {
+ @Override
+ public Void call() {
+ Context context = EpisodesApplication.getInstance().getApplicationContext();
+ ContentResolver resolver = context.getContentResolver();
+ final Uri showUri = ShowsProvider.CONTENT_URI_SHOWS;
+ final String[] projection = {
+ ShowsTable.COLUMN_ID,
+ ShowsTable.COLUMN_NAME
+ };
+ final String sort = ShowsTable.COLUMN_NAME + " ASC";
+ final Cursor cursor = resolver.query(showUri, projection, null, null, sort);
+ final int idColumnIndex = cursor.getColumnIndex(ShowsTable.COLUMN_ID);
+ final int nameColumnIndex = cursor.getColumnIndex(ShowsTable.COLUMN_NAME);
+ final int total = cursor.getCount();
+
+ NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
+ NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, "episodes_channel_id");
+ notificationBuilder
+ .setContentTitle("Refreshing Shows")
+ .setSmallIcon(R.drawable.ic_show_starred)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT);
+ int current = 0;
+ notificationBuilder.setProgress(total, current, false);
+ notificationManager.notify(0, notificationBuilder.build());
+
+ int showId;
+ String showName;
+ cursor.moveToFirst();
+ do {
+ showId = cursor.getInt(idColumnIndex);
+ showName = cursor.getString(nameColumnIndex);
+ notificationBuilder.setContentText(showName);
+ notificationBuilder.setProgress(total, current, false);
+ notificationManager.notify(0, notificationBuilder.build());
+ refreshShow(showId, resolver);
+ current += 1;
+ } while (cursor.moveToNext());
+ cursor.close();
+ notificationBuilder.setContentText("Refresh complete!").setProgress(0, 0, false);
+ notificationManager.notify(0, notificationBuilder.build());
+ return null;
+ }
+}
diff --git a/app/src/main/java/org/jamienicol/episodes/services/RefreshShowService.java b/app/src/main/java/com/redcoracle/episodes/services/RefreshShowService.java
similarity index 92%
rename from app/src/main/java/org/jamienicol/episodes/services/RefreshShowService.java
rename to app/src/main/java/com/redcoracle/episodes/services/RefreshShowService.java
index 6f6a0ac0..da30fe95 100644
--- a/app/src/main/java/org/jamienicol/episodes/services/RefreshShowService.java
+++ b/app/src/main/java/com/redcoracle/episodes/services/RefreshShowService.java
@@ -15,11 +15,12 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes.services;
+package com.redcoracle.episodes.services;
import android.app.IntentService;
import android.content.Intent;
-import org.jamienicol.episodes.RefreshShowUtil;
+
+import com.redcoracle.episodes.RefreshShowUtil;
public class RefreshShowService extends IntentService
{
diff --git a/app/src/main/java/com/redcoracle/episodes/services/RefreshShowTask.java b/app/src/main/java/com/redcoracle/episodes/services/RefreshShowTask.java
new file mode 100644
index 00000000..72a52c2f
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/services/RefreshShowTask.java
@@ -0,0 +1,25 @@
+package com.redcoracle.episodes.services;
+
+import android.content.Context;
+
+import com.redcoracle.episodes.EpisodesApplication;
+import com.redcoracle.episodes.RefreshShowUtil;
+
+import java.util.concurrent.Callable;
+
+public class RefreshShowTask implements Callable {
+ private static final String TAG = DeleteShowTask.class.getName();
+ private int showId;
+ private Context context;
+
+ public RefreshShowTask(int showId) {
+ this.showId = showId;
+ this.context = EpisodesApplication.getInstance().getApplicationContext();
+ }
+
+ @Override
+ public Void call() {
+ RefreshShowUtil.refreshShow(showId, this.context.getContentResolver());
+ return null;
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/services/RestoreTask.java b/app/src/main/java/com/redcoracle/episodes/services/RestoreTask.java
new file mode 100644
index 00000000..c61c3417
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/services/RestoreTask.java
@@ -0,0 +1,63 @@
+package com.redcoracle.episodes.services;
+
+import android.content.Context;
+import android.os.Environment;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.core.content.ContextCompat;
+
+import com.bumptech.glide.Glide;
+import com.redcoracle.episodes.EpisodesApplication;
+import com.redcoracle.episodes.R;
+import com.redcoracle.episodes.db.DatabaseOpenHelper;
+import com.redcoracle.episodes.db.ShowsProvider;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.util.concurrent.Callable;
+
+public class RestoreTask implements Callable {
+ private final static String TAG = RestoreTask.class.getName();
+ private final Context context;
+ private final String filename;
+
+
+ public RestoreTask(String filename) {
+ this.context = EpisodesApplication.getInstance().getApplicationContext();
+ this.filename = filename;
+ }
+
+ public Void call() {
+ if (!isExternalStorageWritable()) {
+ return null;
+ }
+ final File backupFile = new File(this.filename);
+ final File databaseFile = this.context.getDatabasePath(DatabaseOpenHelper.getDbName());
+ try {
+ FileChannel src = new FileInputStream(backupFile).getChannel();
+ FileChannel dest = new FileOutputStream(databaseFile).getChannel();
+ dest.transferFrom(src, 0, src.size());
+ Glide.get(this.context).clearDiskCache();
+ ContextCompat.getMainExecutor(this.context).execute(() -> Toast.makeText(
+ this.context,
+ this.context.getString(R.string.restore_success_message),
+ Toast.LENGTH_LONG
+ ).show());
+ Log.i(TAG, "Library restored successfully.");
+ } catch (IOException e) {
+ Log.e(TAG, String.format("Error restoring library: %s", e.toString()));
+ } finally {
+ ShowsProvider.reloadDatabase(this.context);
+ }
+ return null;
+ }
+
+ private boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state);
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/tvdb/Client.java b/app/src/main/java/com/redcoracle/episodes/tvdb/Client.java
new file mode 100644
index 00000000..0c146313
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/tvdb/Client.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2012-2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes.tvdb;
+
+import android.util.Log;
+
+import com.redcoracle.episodes.EpisodesApplication;
+import com.uwetrottmann.tmdb2.Tmdb;
+import com.uwetrottmann.tmdb2.entities.AppendToResponse;
+import com.uwetrottmann.tmdb2.entities.BaseTvShow;
+import com.uwetrottmann.tmdb2.entities.FindResults;
+import com.uwetrottmann.tmdb2.entities.TvSeason;
+import com.uwetrottmann.tmdb2.entities.TvShow;
+import com.uwetrottmann.tmdb2.entities.TvShowResultsPage;
+import com.uwetrottmann.tmdb2.enumerations.AppendToResponseItem;
+import com.uwetrottmann.tmdb2.enumerations.ExternalSource;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import retrofit2.Response;
+
+public class Client {
+ private static final String TAG = Client.class.getName();
+ private final Tmdb tmdb;
+
+ public Client() {
+ this.tmdb = EpisodesApplication.getInstance().getTmdbClient();
+ }
+
+ public List searchShows(String query, String language) {
+ this.tmdb.searchService().tv(query, null, language, null, false);
+
+ try {
+ final TvShowResultsPage results = this.tmdb
+ .searchService()
+ .tv(query, null, language, null, false)
+ .execute()
+ .body();
+ if (results != null) {
+ final SearchShowsParser parser = new SearchShowsParser();
+ return parser.parse(results, language);
+ } else {
+ return new LinkedList<>();
+ }
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return new LinkedList<>();
+ }
+ }
+
+ public Show getShow(HashMap showIds, String language) {
+ Show show = null;
+ try {
+ TvShow lookupResult = null;
+ AppendToResponse includes = new AppendToResponse(AppendToResponseItem.EXTERNAL_IDS);
+
+ if (showIds.get("tmdbId") != null) {
+ int tmdbId = Integer.parseInt(showIds.get("tmdbId"));
+ Response seriesResponse = this.tmdb.tvService().tv(tmdbId, language, includes).execute();
+ if (seriesResponse.isSuccessful() && seriesResponse.body() != null) {
+ lookupResult = seriesResponse.body();
+ }
+ }
+
+ if (lookupResult == null && showIds.get("tvdbId") != null) {
+ Response seriesResponse = this.tmdb.findService().find(
+ showIds.get("tvdbId"),
+ ExternalSource.TVDB_ID,
+ language
+ ).execute();
+ if (seriesResponse.isSuccessful() && seriesResponse.body() != null) {
+ if (seriesResponse.body().tv_results != null && seriesResponse.body().tv_results.size() > 0) {
+ BaseTvShow sparseShow = seriesResponse.body().tv_results.get(0);
+ lookupResult = tmdb.tvService().tv(sparseShow.id, language, includes).execute().body();
+ }
+ }
+ }
+
+ if (lookupResult == null && showIds.get("imbId") != null) {
+ Response seriesResponse = this.tmdb.findService().find(
+ showIds.get("imbId"),
+ ExternalSource.IMDB_ID,
+ language
+ ).execute();
+ if (seriesResponse.isSuccessful() && seriesResponse.body() != null) {
+ if (seriesResponse.body().tv_results != null && seriesResponse.body().tv_results.size() > 0) {
+ BaseTvShow sparseShow = seriesResponse.body().tv_results.get(0);
+ lookupResult = tmdb.tvService().tv(sparseShow.id, language, includes).execute().body();
+ }
+ }
+ }
+
+ if (lookupResult != null) {
+ final GetShowParser parser = new GetShowParser();
+ show = parser.parse(lookupResult, language);
+ show.setEpisodes(getEpisodesForShow(lookupResult, language));
+ }
+
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ }
+ return show;
+ }
+
+ public Show getShow(int id, String language, boolean includeEpisodes) {
+ try {
+ AppendToResponse includes = new AppendToResponse(AppendToResponseItem.EXTERNAL_IDS);
+ Response seriesResponse = this.tmdb.tvService().tv(id, language, includes).execute();
+ Log.d(TAG, String.format("Received response %d: %s", seriesResponse.code(), seriesResponse.message()));
+ if (seriesResponse.isSuccessful() && seriesResponse.body() != null) {
+ final GetShowParser parser = new GetShowParser();
+ final TvShow series = seriesResponse.body();
+ Show show = parser.parse(series, language);
+
+ if (show != null && includeEpisodes) {
+ ArrayList episodes = getEpisodesForShow(series, language);
+ show.setEpisodes(episodes);
+ }
+ return show;
+ } else {
+ return null;
+ }
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return null;
+ }
+ }
+
+ public ArrayList getEpisodesForShow(TvShow series, String language) {
+ int episode_count = series.number_of_episodes != null ? series.number_of_episodes : 64;
+ ArrayList episodes = new ArrayList<>(episode_count);
+ final GetEpisodesParser episodesParser = new GetEpisodesParser();
+ if (series.number_of_seasons != null) {
+ for (TvSeason season : series.seasons) {
+ try {
+ AppendToResponse includes = new AppendToResponse(AppendToResponseItem.EXTERNAL_IDS);
+ season = this.tmdb.tvSeasonsService().season(series.id, season.season_number, language, includes).execute().body();
+ if (season != null) {
+ episodes.addAll(episodesParser.parse(season.episodes));
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return episodes;
+ }
+}
diff --git a/app/src/main/java/org/jamienicol/episodes/tvdb/Episode.java b/app/src/main/java/com/redcoracle/episodes/tvdb/Episode.java
similarity index 63%
rename from app/src/main/java/org/jamienicol/episodes/tvdb/Episode.java
rename to app/src/main/java/com/redcoracle/episodes/tvdb/Episode.java
index 9ed856c6..60d76f95 100644
--- a/app/src/main/java/org/jamienicol/episodes/tvdb/Episode.java
+++ b/app/src/main/java/com/redcoracle/episodes/tvdb/Episode.java
@@ -15,26 +15,27 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes.tvdb;
+package com.redcoracle.episodes.tvdb;
import java.util.Date;
-public class Episode
-{
+public class Episode {
private int id;
+ private Integer tvdbId;
+ private Integer tmdbId;
+ private String imdbId;
private String name;
+ private String language;
private String overview;
private int episodeNumber;
private int seasonNumber;
private Date firstAired;
- public Episode() {
- id = 0;
- name = "";
- overview = "";
- episodeNumber = 0;
- seasonNumber = 0;
- firstAired = null;
+ Episode() {
+ }
+
+ public String identifier() {
+ return String.format("%s-%s", this.seasonNumber, this.episodeNumber);
}
public int getId() {
@@ -45,6 +46,30 @@ public void setId(int id) {
this.id = id;
}
+ public Integer getTvdbId() {
+ return this.tvdbId;
+ }
+
+ public void setTvdbId(Integer id) {
+ this.tvdbId = id;
+ }
+
+ public int getTmdbId() {
+ return this.tmdbId;
+ }
+
+ public void setTmdbId(int id) {
+ this.tmdbId = id;
+ }
+
+ public String getImdbId() {
+ return this.imdbId;
+ }
+
+ public void setImdbId(String id) {
+ this.imdbId = id;
+ }
+
public String getName() {
return name;
}
@@ -53,6 +78,14 @@ public void setName(String name) {
this.name = name;
}
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
public String getOverview() {
return overview;
}
@@ -65,7 +98,7 @@ public int getEpisodeNumber() {
return episodeNumber;
}
- public void setEpisodeNumber(int episodeNumber) {
+ void setEpisodeNumber(int episodeNumber) {
this.episodeNumber = episodeNumber;
}
@@ -73,7 +106,7 @@ public int getSeasonNumber() {
return seasonNumber;
}
- public void setSeasonNumber(int seasonNumber) {
+ void setSeasonNumber(int seasonNumber) {
this.seasonNumber = seasonNumber;
}
@@ -81,7 +114,7 @@ public Date getFirstAired() {
return firstAired;
}
- public void setFirstAired(Date firstAired) {
+ void setFirstAired(Date firstAired) {
this.firstAired = firstAired;
}
}
diff --git a/app/src/main/java/com/redcoracle/episodes/tvdb/GetEpisodesParser.java b/app/src/main/java/com/redcoracle/episodes/tvdb/GetEpisodesParser.java
new file mode 100644
index 00000000..86797f5c
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/tvdb/GetEpisodesParser.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012-2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes.tvdb;
+
+import android.util.Log;
+
+import com.uwetrottmann.tmdb2.entities.TvEpisode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class GetEpisodesParser {
+ private static final String TAG = GetEpisodesParser.class.getName();
+
+ ArrayList parse(List tmdbEpisodes) {
+ try {
+ ArrayList episodes = new ArrayList<>(tmdbEpisodes.size());
+ for (TvEpisode episode : tmdbEpisodes) {
+ Episode e = new Episode();
+ e.setId(episode.id);
+ e.setTmdbId(episode.id);
+ if (episode.external_ids != null) {
+ if (episode.external_ids.tvdb_id != null) {
+ e.setTvdbId(episode.external_ids.tvdb_id);
+ }
+ if (episode.external_ids.imdb_id != null) {
+ e.setImdbId(episode.external_ids.imdb_id);
+ }
+ }
+ e.setName(episode.name != null ? episode.name : "");
+ e.setOverview(episode.overview);
+ e.setSeasonNumber(episode.season_number);
+ e.setEpisodeNumber(episode.episode_number);
+ e.setFirstAired(episode.air_date);
+ episodes.add(e);
+ }
+ return episodes;
+ } catch (Exception ex) {
+ Log.w(TAG, ex);
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/tvdb/GetShowParser.java b/app/src/main/java/com/redcoracle/episodes/tvdb/GetShowParser.java
new file mode 100644
index 00000000..16875d33
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/tvdb/GetShowParser.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2012-2014 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes.tvdb;
+
+import android.util.Log;
+
+import com.uwetrottmann.tmdb2.entities.TvShow;
+
+class GetShowParser {
+ private static final String TAG = "GetShowParser";
+
+ Show parse(TvShow series, String language) {
+ Show show;
+ try {
+ show = new Show();
+ if (series.id != null) {
+ show.setId(series.id);
+ show.setTmdbId(series.id);
+ } else {
+ Log.w(TAG, String.format("Show does not have an ID: %s", series.name));
+ return null;
+ }
+ if (series.external_ids != null) {
+ if (series.external_ids.tvdb_id != null) {
+ show.setTvdbId(series.external_ids.tvdb_id);
+ }
+ if (series.external_ids.imdb_id != null) {
+ show.setImdbId(series.external_ids.imdb_id);
+ }
+ }
+ show.setName(series.name);
+ show.setLanguage(language);
+ show.setOverview(series.overview);
+ show.setFirstAired(series.first_air_date);
+ show.setBannerPath(series.backdrop_path);
+ show.setPosterPath(series.poster_path);
+ } catch (Exception e) {
+ Log.w(TAG, e);
+ return null;
+ }
+ return show;
+ }
+}
diff --git a/app/src/main/java/com/redcoracle/episodes/tvdb/SearchShowsParser.java b/app/src/main/java/com/redcoracle/episodes/tvdb/SearchShowsParser.java
new file mode 100644
index 00000000..767bde3b
--- /dev/null
+++ b/app/src/main/java/com/redcoracle/episodes/tvdb/SearchShowsParser.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2012 Jamie Nicol
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.redcoracle.episodes.tvdb;
+
+import android.util.Log;
+
+import com.uwetrottmann.tmdb2.entities.BaseTvShow;
+import com.uwetrottmann.tmdb2.entities.TvShowResultsPage;
+
+import java.util.LinkedList;
+import java.util.List;
+
+class SearchShowsParser {
+ private static final String TAG = SearchShowsParser.class.getName();
+
+ private List parsed;
+
+ List parse(TvShowResultsPage results, String language) {
+ try {
+ List series = results.results;
+ parsed = new LinkedList<>();
+ for(BaseTvShow s : series) {
+ Show show = new Show();
+ show.setId(s.id);
+ show.setTmdbId(s.id);
+ show.setName(s.name);
+ show.setLanguage(language);
+ show.setOverview(s.overview);
+ show.setFirstAired(s.first_air_date);
+ parsed.add(show);
+ }
+ } catch (Exception e) {
+ Log.w(TAG, e);
+ }
+ return parsed;
+ }
+
+}
diff --git a/app/src/main/java/org/jamienicol/episodes/tvdb/Show.java b/app/src/main/java/com/redcoracle/episodes/tvdb/Show.java
similarity index 72%
rename from app/src/main/java/org/jamienicol/episodes/tvdb/Show.java
rename to app/src/main/java/com/redcoracle/episodes/tvdb/Show.java
index afbafd86..9b29f8ce 100644
--- a/app/src/main/java/org/jamienicol/episodes/tvdb/Show.java
+++ b/app/src/main/java/com/redcoracle/episodes/tvdb/Show.java
@@ -15,15 +15,18 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes.tvdb;
+package com.redcoracle.episodes.tvdb;
import java.util.Date;
import java.util.List;
-public class Show
-{
+public class Show {
private int id;
+ private int tvdbId;
+ private int tmdbId;
+ private String imdbId;
private String name;
+ private String language;
private String overview;
private Date firstAired;
private String bannerPath;
@@ -32,8 +35,8 @@ public class Show
private List episodes;
public Show() {
- id = 0;
name = "";
+ language = "";
overview = "";
firstAired = null;
bannerPath = "";
@@ -50,6 +53,30 @@ public void setId(int id) {
this.id = id;
}
+ public int getTvdbId() {
+ return tvdbId;
+ }
+
+ public void setTvdbId(int tvdbId) {
+ this.tvdbId = tvdbId;
+ }
+
+ public int getTmdbId() {
+ return this.tmdbId;
+ }
+
+ public void setTmdbId(int tmdbId) {
+ this.tmdbId = tmdbId;
+ }
+
+ public String getImdbId() {
+ return this.imdbId;
+ }
+
+ public void setImdbId(String imdbId) {
+ this.imdbId = imdbId;
+ }
+
public String getName() {
return name;
}
@@ -58,6 +85,14 @@ public void setName(String name) {
this.name = name;
}
+ public String getLanguage() {
+ return language;
+ }
+
+ void setLanguage(String language) {
+ this.language = language;
+ }
+
public String getOverview() {
return overview;
}
@@ -70,7 +105,7 @@ public Date getFirstAired() {
return firstAired;
}
- public void setFirstAired(Date firstAired) {
+ void setFirstAired(Date firstAired) {
this.firstAired = firstAired;
}
@@ -78,7 +113,7 @@ public String getBannerPath() {
return bannerPath;
}
- public void setBannerPath(String bannerPath) {
+ void setBannerPath(String bannerPath) {
this.bannerPath = bannerPath;
}
@@ -86,7 +121,7 @@ public String getFanartPath() {
return fanartPath;
}
- public void setFanartPath(String fanartPath) {
+ void setFanartPath(String fanartPath) {
this.fanartPath = fanartPath;
}
diff --git a/app/src/main/java/org/jamienicol/episodes/widget/ObservableScrollView.java b/app/src/main/java/com/redcoracle/episodes/widget/ObservableScrollView.java
similarity index 97%
rename from app/src/main/java/org/jamienicol/episodes/widget/ObservableScrollView.java
rename to app/src/main/java/com/redcoracle/episodes/widget/ObservableScrollView.java
index ae126497..5c303451 100644
--- a/app/src/main/java/org/jamienicol/episodes/widget/ObservableScrollView.java
+++ b/app/src/main/java/com/redcoracle/episodes/widget/ObservableScrollView.java
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes.widget;
+package com.redcoracle.episodes.widget;
import android.content.Context;
import android.util.AttributeSet;
diff --git a/app/src/main/java/org/jamienicol/episodes/widget/WrapContentListView.java b/app/src/main/java/com/redcoracle/episodes/widget/WrapContentListView.java
similarity index 97%
rename from app/src/main/java/org/jamienicol/episodes/widget/WrapContentListView.java
rename to app/src/main/java/com/redcoracle/episodes/widget/WrapContentListView.java
index 03487af8..6b23bec4 100644
--- a/app/src/main/java/org/jamienicol/episodes/widget/WrapContentListView.java
+++ b/app/src/main/java/com/redcoracle/episodes/widget/WrapContentListView.java
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes.widget;
+package com.redcoracle.episodes.widget;
import android.content.Context;
import android.util.AttributeSet;
diff --git a/app/src/main/java/org/jamienicol/episodes/widget/WrapContentViewPager.java b/app/src/main/java/com/redcoracle/episodes/widget/WrapContentViewPager.java
similarity index 91%
rename from app/src/main/java/org/jamienicol/episodes/widget/WrapContentViewPager.java
rename to app/src/main/java/com/redcoracle/episodes/widget/WrapContentViewPager.java
index 97bcdf6c..7e093ef3 100644
--- a/app/src/main/java/org/jamienicol/episodes/widget/WrapContentViewPager.java
+++ b/app/src/main/java/com/redcoracle/episodes/widget/WrapContentViewPager.java
@@ -15,15 +15,16 @@
* along with this program. If not, see .
*/
-package org.jamienicol.episodes.widget;
+package com.redcoracle.episodes.widget;
import android.content.Context;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.FragmentPagerAdapter;
-import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.View;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentPagerAdapter;
+import androidx.viewpager.widget.ViewPager;
+
public class WrapContentViewPager
extends ViewPager
{
diff --git a/app/src/main/java/org/jamienicol/episodes/EpisodesApplication.java b/app/src/main/java/org/jamienicol/episodes/EpisodesApplication.java
deleted file mode 100644
index c224c0ef..00000000
--- a/app/src/main/java/org/jamienicol/episodes/EpisodesApplication.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes;
-
-import android.app.Application;
-import android.preference.PreferenceManager;
-import android.util.Log;
-import com.nostra13.universalimageloader.core.ImageLoader;
-import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
-import com.squareup.okhttp.Cache;
-import com.squareup.okhttp.OkHttpClient;
-import java.io.IOException;
-
-public class EpisodesApplication
- extends Application
-{
- private static final String TAG = EpisodesApplication.class.getName();
-
- private static EpisodesApplication instance;
-
- private AutoRefreshHelper autoRefreshHelper;
- private OkHttpClient httpClient;
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- instance = this;
-
- // ensure the default settings are initialised at first launch,
- // rather than waiting for the settings screen to be opened.
- // do this before anything that needs these settings is instantiated.
- PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
-
- autoRefreshHelper = AutoRefreshHelper.getInstance(this);
-
- httpClient = new OkHttpClient();
- try {
- Cache httpCache = new Cache(getCacheDir(), 1024 * 1024);
- httpClient.setCache(httpCache);
- } catch (IOException e) {
- Log.w(TAG, "Error initialising okhttp cache", e);
- }
-
- final ImageLoaderConfiguration imageLoaderConfig =
- new ImageLoaderConfiguration.Builder(this)
- .build();
-
- ImageLoader.getInstance().init(imageLoaderConfig);
- }
-
- public static EpisodesApplication getInstance() {
- return instance;
- }
-
- public OkHttpClient getHttpClient() {
- return httpClient;
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/MainActivity.java b/app/src/main/java/org/jamienicol/episodes/MainActivity.java
deleted file mode 100644
index 91e7ca76..00000000
--- a/app/src/main/java/org/jamienicol/episodes/MainActivity.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v4.app.FragmentManager;
-import android.support.v4.view.MenuItemCompat;
-import android.support.v7.app.ActionBarActivity;
-import android.support.v7.widget.SearchView;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import org.jamienicol.episodes.db.BackUpRestoreHelper;
-
-public class MainActivity
- extends ActionBarActivity
- implements ShowsListFragment.OnShowSelectedListener,
- SelectBackupDialog.OnBackupSelectedListener
-{
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main_activity);
-
- // ensure that the auto-refresh alarm is scheduled.
- // this should mainly be useful the first time the app is ran.
- AutoRefreshHelper.getInstance(getApplicationContext())
- .rescheduleAlarm();
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- final MenuInflater inflater = getMenuInflater();
- inflater.inflate(R.menu.main, menu);
-
- final MenuItem menuItem = menu.findItem(R.id.menu_add_new_show);
- final SearchView addShow =
- (SearchView)MenuItemCompat.getActionView(menuItem);
- addShow.setQueryHint(getString(R.string.menu_add_show_search_hint));
- addShow.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
- @Override
- public boolean onQueryTextChange(String query) {
- return true;
- }
-
- @Override
- public boolean onQueryTextSubmit(String query) {
- final Intent intent =
- new Intent(MainActivity.this,
- AddShowSearchActivity.class);
- intent.putExtra("query", query);
- startActivity(intent);
- MenuItemCompat.collapseActionView(menuItem);
- return true;
- }
- });
-
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_back_up:
- back_up();
- return true;
-
- case R.id.menu_restore:
- restore();
- return true;
-
- case R.id.menu_settings:
- showSettings();
- return true;
-
- case R.id.menu_about:
- showAbout();
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public void onShowSelected(int showId) {
- final Intent intent = new Intent(this, ShowActivity.class);
- intent.putExtra("showId", showId);
- startActivity(intent);
- }
-
- private void back_up() {
- BackUpRestoreHelper.backUp(getApplicationContext());
- }
-
- private void restore() {
- final FragmentManager fm = getSupportFragmentManager();
- final SelectBackupDialog dialog = new SelectBackupDialog();
- dialog.show(fm, "select_backup_dialog");
- }
-
- @Override
- public void onBackupSelected(String backupFilename) {
- BackUpRestoreHelper.restore(getApplicationContext(), backupFilename);
- }
-
- private void showSettings() {
- final Intent intent = new Intent(this, SettingsActivity.class);
- startActivity(intent);
- }
-
- private void showAbout() {
- final Intent intent = new Intent(this, AboutActivity.class);
- startActivity(intent);
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/RefreshShowUtil.java b/app/src/main/java/org/jamienicol/episodes/RefreshShowUtil.java
deleted file mode 100644
index 0a32097c..00000000
--- a/app/src/main/java/org/jamienicol/episodes/RefreshShowUtil.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Copyright (C) 2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.util.Log;
-import java.util.List;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsTable;
-import org.jamienicol.episodes.db.ShowsProvider;
-import org.jamienicol.episodes.tvdb.Client;
-import org.jamienicol.episodes.tvdb.Episode;
-import org.jamienicol.episodes.tvdb.Show;
-
-public class RefreshShowUtil
-{
- private static final String TAG = RefreshShowUtil.class.getName();
-
- public static void refreshShow(int showId,
- ContentResolver contentResolver) {
- final Client tvdbClient = new Client("25B864A8BC56AFAD");
-
- Log.i(TAG, String.format("Refreshing show %d", showId));
-
- final int showTvdbId = getShowTvdbId(showId, contentResolver);
- // fetch full show + episode information from tvdb
- final Show show = tvdbClient.getShow(showTvdbId);
-
- if (show != null) {
- updateShow(showId, show, contentResolver);
- updateExistingEpisodes(showId, show.getEpisodes(), contentResolver);
- addNewEpisodes(showId, show.getEpisodes(), contentResolver);
- }
- }
-
- private static int getShowTvdbId(int showId,
- ContentResolver contentResolver) {
- final Uri showUri =
- Uri.withAppendedPath(ShowsProvider.CONTENT_URI_SHOWS,
- String.valueOf(showId));
- final String[] projection = {
- ShowsTable.COLUMN_TVDB_ID
- };
-
- final Cursor showCursor = contentResolver.query(showUri,
- projection,
- null,
- null,
- null);
-
- final int tvdbIdColumnIndex =
- showCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_TVDB_ID);
- showCursor.moveToFirst();
-
- return showCursor.getInt(tvdbIdColumnIndex);
- }
-
- private static void updateShow(int showId,
- Show show,
- ContentResolver contentResolver) {
-
- final ContentValues showValues = new ContentValues();
- showValues.put(ShowsTable.COLUMN_TVDB_ID, show.getId());
- showValues.put(ShowsTable.COLUMN_NAME, show.getName());
- showValues.put(ShowsTable.COLUMN_OVERVIEW, show.getOverview());
- if (show.getFirstAired() != null) {
- showValues.put(ShowsTable.COLUMN_FIRST_AIRED,
- show.getFirstAired().getTime() / 1000);
- }
- showValues.put(ShowsTable.COLUMN_BANNER_PATH, show.getBannerPath());
- showValues.put(ShowsTable.COLUMN_FANART_PATH, show.getFanartPath());
- showValues.put(ShowsTable.COLUMN_POSTER_PATH, show.getPosterPath());
-
- final Uri showUri =
- Uri.withAppendedPath(ShowsProvider.CONTENT_URI_SHOWS,
- String.valueOf(showId));
- contentResolver.update(showUri, showValues, null, null);
- }
-
- private static void updateExistingEpisodes(int showId,
- List episodes,
- ContentResolver contentResolver) {
- final Cursor episodesCursor =
- getEpisodesCursor(showId, contentResolver);
-
- while (episodesCursor.moveToNext()) {
-
- final int idColumnIndex =
- episodesCursor.getColumnIndexOrThrow(EpisodesTable.COLUMN_ID);
- final int episodeId = episodesCursor.getInt(idColumnIndex);
- final Uri episodeUri =
- Uri.withAppendedPath(ShowsProvider.CONTENT_URI_EPISODES,
- String.valueOf(episodeId));
-
- final int tvdbIdColumnIndex =
- episodesCursor.getColumnIndexOrThrow(EpisodesTable.COLUMN_TVDB_ID);
- final int episodeTvdbId = episodesCursor.getInt(tvdbIdColumnIndex);
- final Episode episode = findEpisodeWithTvdbId(episodes,
- episodeTvdbId);
-
- if (episode == null) {
- /* the episode no longer exists in tvdb. delete */
- Log.i(TAG, String.format("Deleting episode %d: no longer exists in tvdb.", episodeId));
- contentResolver.delete(episodeUri, null, null);
-
- } else {
- /* update the episode row with the new values */
- final ContentValues epValues = new ContentValues();
- epValues.put(EpisodesTable.COLUMN_TVDB_ID, episode.getId());
- epValues.put(EpisodesTable.COLUMN_SHOW_ID, showId);
- epValues.put(EpisodesTable.COLUMN_NAME, episode.getName());
- epValues.put(EpisodesTable.COLUMN_OVERVIEW,
- episode.getOverview());
- epValues.put(EpisodesTable.COLUMN_EPISODE_NUMBER,
- episode.getEpisodeNumber());
- epValues.put(EpisodesTable.COLUMN_SEASON_NUMBER,
- episode.getSeasonNumber());
- if (episode.getFirstAired() != null) {
- epValues.put(EpisodesTable.COLUMN_FIRST_AIRED,
- episode.getFirstAired().getTime() / 1000);
- }
-
- Log.i(TAG, String.format("Updating episode %d.", episodeId));
- contentResolver.update(episodeUri, epValues, null, null);
-
- /* remove episode from list of episodes
- * returned by tvdb. by the end of this function
- * this list will only contain new episodes */
- episodes.remove(episode);
- }
- }
- }
-
- private static Cursor getEpisodesCursor(int showId,
- ContentResolver contentResolver) {
- final String[] projection = {
- EpisodesTable.COLUMN_ID,
- EpisodesTable.COLUMN_TVDB_ID
- };
- final String selection = String.format("%s=?",
- EpisodesTable.COLUMN_SHOW_ID);
- final String[] selectionArgs = {
- String.valueOf(showId)
- };
-
- final Cursor cursor =
- contentResolver.query(ShowsProvider.CONTENT_URI_EPISODES,
- projection,
- selection,
- selectionArgs,
- null);
-
- return cursor;
- }
-
- private static Episode findEpisodeWithTvdbId(List episodes,
- int episodeTvdbId) {
- for (Episode ep : episodes) {
- if (ep.getId() == episodeTvdbId) {
- return ep;
- }
- }
-
- return null;
- }
-
- private static void addNewEpisodes(int showId,
- List episodes,
- ContentResolver contentResolver) {
-
- for (Episode episode : episodes) {
- final ContentValues epValues = new ContentValues();
- epValues.put(EpisodesTable.COLUMN_TVDB_ID, episode.getId());
- epValues.put(EpisodesTable.COLUMN_SHOW_ID, showId);
- epValues.put(EpisodesTable.COLUMN_NAME, episode.getName());
- epValues.put(EpisodesTable.COLUMN_OVERVIEW,
- episode.getOverview());
- epValues.put(EpisodesTable.COLUMN_EPISODE_NUMBER,
- episode.getEpisodeNumber());
- epValues.put(EpisodesTable.COLUMN_SEASON_NUMBER,
- episode.getSeasonNumber());
- if (episode.getFirstAired() != null) {
- epValues.put(EpisodesTable.COLUMN_FIRST_AIRED,
- episode.getFirstAired().getTime() / 1000);
- }
-
- Log.i(TAG, "Adding new episode.");
- contentResolver.insert(ShowsProvider.CONTENT_URI_EPISODES,
- epValues);
- }
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/ShowsListFragment.java b/app/src/main/java/org/jamienicol/episodes/ShowsListFragment.java
deleted file mode 100644
index 4ad1feea..00000000
--- a/app/src/main/java/org/jamienicol/episodes/ShowsListFragment.java
+++ /dev/null
@@ -1,495 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes;
-
-import android.app.Activity;
-import android.content.AsyncQueryHandler;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.support.v4.app.ListFragment;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.CursorLoader;
-import android.support.v4.content.Loader;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.CompoundButton;
-import android.widget.ImageView;
-import android.widget.ListView;
-import android.widget.ProgressBar;
-import android.widget.TextView;
-import android.widget.ToggleButton;
-import com.nostra13.universalimageloader.core.DisplayImageOptions;
-import com.nostra13.universalimageloader.core.ImageLoader;
-import java.util.ArrayList;
-import java.util.List;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsProvider;
-import org.jamienicol.episodes.db.ShowsTable;
-import org.jamienicol.episodes.services.RefreshShowService;
-
-public class ShowsListFragment
- extends ListFragment
- implements LoaderManager.LoaderCallbacks
-{
- private static final int LOADER_ID_SHOWS = 0;
- private static final int LOADER_ID_EPISODES = 1;
-
- private static final String KEY_PREF_SHOWS_FILTER = "pref_shows_filter";
-
- private static final int SHOWS_FILTER_ALL = 0;
- private static final int SHOWS_FILTER_STARRED = 1;
- private static final int SHOWS_FILTER_UNCOMPLETED = 2;
-
- private ShowsListAdapter listAdapter;
- private Cursor showsData;
- private Cursor episodesData;
-
- public interface OnShowSelectedListener {
- public void onShowSelected(int showId);
- }
- private OnShowSelectedListener onShowSelectedListener;
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- try {
- onShowSelectedListener = (OnShowSelectedListener)activity;
- } catch (ClassCastException e) {
- final String message =
- String.format("%s must implement OnShowSelectedListener",
- activity.toString());
- throw new ClassCastException(message);
- }
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setHasOptionsMenu(true);
- }
-
- public View onCreateView(LayoutInflater inflater,
- ViewGroup container,
- Bundle savedInstanceState) {
- return inflater.inflate(R.layout.shows_list_fragment, container, false);
- }
-
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
-
- listAdapter = new ShowsListAdapter(getActivity(),
- null,
- null);
- setListAdapter(listAdapter);
-
- getLoaderManager().initLoader(LOADER_ID_SHOWS, null, this);
- getLoaderManager().initLoader(LOADER_ID_EPISODES, null, this);
- }
-
- @Override
- public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
- inflater.inflate(R.menu.shows_list_fragment, menu);
- }
-
- @Override
- public void onPrepareOptionsMenu(Menu menu) {
-
- // hide refresh all option if no shows exist
- final boolean showsExist =
- (showsData != null && showsData.moveToFirst());
-
- menu.findItem(R.id.menu_refresh_all_shows).setVisible(showsExist);
-
- /* set the currently selected filter's menu item as checked */
- final SharedPreferences prefs =
- PreferenceManager.getDefaultSharedPreferences(getActivity());
- final int filter =
- prefs.getInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_ALL);
-
- switch (filter) {
- case SHOWS_FILTER_ALL:
- menu.findItem(R.id.menu_filter_all).setChecked(true);
- break;
- case SHOWS_FILTER_STARRED:
- menu.findItem(R.id.menu_filter_starred).setChecked(true);
- break;
- case SHOWS_FILTER_UNCOMPLETED:
- menu.findItem(R.id.menu_filter_uncompleted).setChecked(true);
- break;
- }
-
- super.onPrepareOptionsMenu(menu);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_refresh_all_shows:
- refreshAllShows();
- return true;
-
- case R.id.menu_filter_all:
- case R.id.menu_filter_starred:
- case R.id.menu_filter_uncompleted:
- if (!item.isChecked()) {
- item.setChecked(true);
- }
-
- final SharedPreferences prefs =
- PreferenceManager.getDefaultSharedPreferences(getActivity());
- final SharedPreferences.Editor editor = prefs.edit();
- if (item.getItemId() == R.id.menu_filter_all) {
- editor.putInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_ALL);
- } else if (item.getItemId() == R.id.menu_filter_starred) {
- editor.putInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_STARRED);
- } else if (item.getItemId() == R.id.menu_filter_uncompleted) {
- editor.putInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_UNCOMPLETED);
- }
- editor.apply();
-
- return true;
-
- default:
- return super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public Loader onCreateLoader(int id, Bundle args) {
- if (id == LOADER_ID_SHOWS) {
- final String[] projection = {
- ShowsTable.COLUMN_ID,
- ShowsTable.COLUMN_NAME,
- ShowsTable.COLUMN_STARRED,
- ShowsTable.COLUMN_BANNER_PATH
- };
- return new CursorLoader(getActivity(),
- ShowsProvider.CONTENT_URI_SHOWS,
- projection,
- null,
- null,
- ShowsTable.COLUMN_STARRED + " DESC," +
- ShowsTable.COLUMN_NAME + " ASC");
-
- } else if (id == LOADER_ID_EPISODES) {
- final String[] projection = {
- EpisodesTable.COLUMN_SHOW_ID,
- EpisodesTable.COLUMN_SEASON_NUMBER,
- EpisodesTable.COLUMN_FIRST_AIRED,
- EpisodesTable.COLUMN_WATCHED
- };
- final String selection =
- String.format("%s!=?", EpisodesTable.COLUMN_SEASON_NUMBER);
- final String[] selectionArgs = {
- "0"
- };
- return new CursorLoader(getActivity(),
- ShowsProvider.CONTENT_URI_EPISODES,
- projection,
- selection,
- selectionArgs,
- null);
-
- } else {
- throw new IllegalArgumentException("invalid loader id");
- }
- }
-
- @Override
- public void onLoadFinished(Loader loader, Cursor data) {
- switch (loader.getId()) {
- case LOADER_ID_SHOWS:
- showsData = data;
- listAdapter.swapShowsCursor(data);
- break;
-
- case LOADER_ID_EPISODES:
- episodesData = data;
- listAdapter.swapEpisodesCursor(data);
- break;
- }
-
- getActivity().supportInvalidateOptionsMenu();
- }
-
- @Override
- public void onLoaderReset(Loader loader) {
- onLoadFinished(loader, null);
- }
-
- @Override
- public void onListItemClick(ListView l, View v, int position, long id) {
- onShowSelectedListener.onShowSelected((int)id);
- }
-
- private void refreshAllShows() {
- if (showsData != null && showsData.moveToFirst()) {
- do {
- final int idColumnIndex =
- showsData.getColumnIndexOrThrow(ShowsTable.COLUMN_ID);
-
- final int id = showsData.getInt(idColumnIndex);
-
- final Intent intent = new Intent(getActivity(),
- RefreshShowService.class);
- intent.putExtra("showId", id);
-
- getActivity().startService(intent);
-
- } while (showsData.moveToNext());
- }
- }
-
- private static class ShowsListAdapter
- extends BaseAdapter
- implements SharedPreferences.OnSharedPreferenceChangeListener
- {
- private Context context;
- private Cursor showsCursor;
- private int filter;
- private EpisodesCounter episodesCounter;
-
- // list of shows to be displayed with current filter. maps from
- // the show's position in the list to its position in the cursor.
- private List filteredShows;
-
- public ShowsListAdapter(Context context,
- Cursor showsCursor,
- Cursor episodesCursor) {
- this.context = context;
-
- episodesCounter = new EpisodesCounter(EpisodesTable.COLUMN_SHOW_ID);
- episodesCounter.swapCursor(episodesCursor);
-
- final SharedPreferences prefs =
- PreferenceManager.getDefaultSharedPreferences(context);
- prefs.registerOnSharedPreferenceChangeListener(this);
- filter = prefs.getInt(KEY_PREF_SHOWS_FILTER, SHOWS_FILTER_ALL);
-
- filteredShows = new ArrayList();
-
- swapShowsCursor(showsCursor);
- }
-
- public void swapShowsCursor(Cursor showsCursor) {
- this.showsCursor = showsCursor;
-
- updateFilter();
- notifyDataSetChanged();
- }
-
- public void swapEpisodesCursor(Cursor episodesCursor) {
- episodesCounter.swapCursor(episodesCursor);
-
- if (showsCursor != null) {
- updateFilter();
- notifyDataSetChanged();
- }
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
- String key) {
- if (key.equals(KEY_PREF_SHOWS_FILTER)) {
- filter = sharedPreferences.getInt(KEY_PREF_SHOWS_FILTER,
- SHOWS_FILTER_ALL);
-
- if (showsCursor != null) {
- updateFilter();
- notifyDataSetChanged();
- }
- }
- }
-
- private void updateFilter() {
- filteredShows.clear();
-
- if (showsCursor == null || !showsCursor.moveToFirst()) {
- return;
- }
-
- do {
- switch (filter) {
- case SHOWS_FILTER_STARRED:
- final int starredColumnIndex =
- showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_STARRED);
- if (showsCursor.getInt(starredColumnIndex) > 0) {
- filteredShows.add(showsCursor.getPosition());
- }
- break;
-
- case SHOWS_FILTER_UNCOMPLETED:
- final int idColumnIndex =
- showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ID);
- final int id = showsCursor.getInt(idColumnIndex);
-
- if (episodesCounter.getNumWatchedEpisodes(id) <
- episodesCounter.getNumAiredEpisodes(id)) {
- filteredShows.add(showsCursor.getPosition());
- }
- break;
-
- default:
- filteredShows.add(showsCursor.getPosition());
- break;
- }
- } while (showsCursor.moveToNext());
- }
-
- @Override
- public int getCount() {
- if (showsCursor == null) {
- return 0;
- } else {
- return filteredShows.size();
- }
- }
-
- @Override
- public Object getItem(int position) {
- return null;
- }
-
- @Override
- public long getItemId(int position) {
- showsCursor.moveToPosition(filteredShows.get(position));
-
- final int idColumnIndex =
- showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ID);
- return showsCursor.getInt(idColumnIndex);
- }
-
- @Override
- public View getView(int position,
- View convertView,
- ViewGroup parent) {
-
- final LayoutInflater inflater = LayoutInflater.from(context);
- if(convertView == null) {
- convertView = inflater.inflate(R.layout.shows_list_item,
- parent,
- false);
- }
-
- showsCursor.moveToPosition(filteredShows.get(position));
-
- final int idColumnIndex =
- showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_ID);
- final int id = showsCursor.getInt(idColumnIndex);
-
- final ContentResolver contentResolver =
- context.getContentResolver();
-
- final TextView nameView =
- (TextView)convertView.findViewById(R.id.show_name_view);
- final int nameColumnIndex =
- showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_NAME);
- final String name = showsCursor.getString(nameColumnIndex);
- nameView.setText(name);
-
- final ImageView bannerView =
- (ImageView)convertView.findViewById(R.id.banner_view);
- final int bannerPathColumnIndex =
- showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_BANNER_PATH);
- final String bannerPath = showsCursor.getString(bannerPathColumnIndex);
-
- bannerView.setImageResource(R.drawable.blank_show_banner);
- if (bannerPath != null && !bannerPath.equals("")) {
- final String bannerUrl =
- String.format("http://thetvdb.com/banners/%s", bannerPath);
-
- final DisplayImageOptions options =
- new DisplayImageOptions.Builder()
- .cacheInMemory(true)
- .cacheOnDisk(true)
- .build();
- ImageLoader.getInstance().displayImage(bannerUrl,
- bannerView,
- options);
- }
-
- final ToggleButton starredToggle =
- (ToggleButton)convertView.findViewById(R.id.show_starred_toggle);
- final int starredColumnIndex =
- showsCursor.getColumnIndexOrThrow(ShowsTable.COLUMN_STARRED);
- final boolean starred =
- showsCursor.getInt(starredColumnIndex) > 0 ? true : false;
-
- starredToggle.setOnCheckedChangeListener(null);
- starredToggle.setChecked(starred);
-
- starredToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
- public void onCheckedChanged(CompoundButton buttonView,
- boolean isChecked) {
- final AsyncQueryHandler handler =
- new AsyncQueryHandler(contentResolver) {};
- final ContentValues showValues = new ContentValues();
- showValues.put(ShowsTable.COLUMN_STARRED, isChecked);
-
- final Uri showUri =
- Uri.withAppendedPath(ShowsProvider.CONTENT_URI_SHOWS,
- String.valueOf(id));
- handler.startUpdate(0,
- null,
- showUri,
- showValues,
- null,
- null);
- }
- });
-
- final int numAired = episodesCounter.getNumAiredEpisodes(id);
- final int numWatched = episodesCounter.getNumWatchedEpisodes(id);
- final int numUpcoming = episodesCounter.getNumUpcomingEpisodes(id);
-
- final ProgressBar progressBar =
- (ProgressBar)convertView.findViewById(R.id.show_progress_bar);
- progressBar.setMax(numAired);
- progressBar.setProgress(numWatched);
-
- final TextView watchedCountView =
- (TextView)convertView.findViewById(R.id.watched_count_view);
- String watchedCountText = context.getString(R.string.watched_count,
- numWatched,
- numAired);
- if (numUpcoming != 0) {
- watchedCountText += " " +
- context.getString(R.string.upcoming_count,
- numUpcoming);
- }
- watchedCountView.setText(watchedCountText);
-
- return convertView;
- }
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/db/BackUpRestoreHelper.java b/app/src/main/java/org/jamienicol/episodes/db/BackUpRestoreHelper.java
deleted file mode 100644
index c6a08437..00000000
--- a/app/src/main/java/org/jamienicol/episodes/db/BackUpRestoreHelper.java
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes.db;
-
-import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Environment;
-import android.util.Log;
-import android.widget.Toast;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.nio.channels.FileChannel;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import org.jamienicol.episodes.R;
-
-public class BackUpRestoreHelper
-{
- private final static String TAG = BackUpRestoreHelper.class.getName();
-
- public static void backUp(Context context) {
- final BackUpTask task = new BackUpTask(context);
- task.execute();
- }
-
- public static void restore(Context context, String filename) {
- final RestoreTask task = new RestoreTask(context);
- task.execute(filename);
- }
-
- public static File getBackupDir() {
- return new File(Environment.getExternalStorageDirectory(), "episodes");
- }
-
- private static class BackUpTask
- extends AsyncTask
- {
- private final Context context;
- private String destFilePath;
-
- public BackUpTask(Context context) {
- this.context = context;
- }
-
- @Override
- protected Boolean doInBackground(Void... params) {
- Log.i(TAG, "Backing up library.");
-
- final File srcFile =
- context.getDatabasePath(DatabaseOpenHelper.getDbName());
- if (!srcFile.canRead()) {
- Log.e(TAG, String.format("Cannot read database file: '%s'.",
- srcFile.getPath()));
- return false;
- }
-
- final File destDir = getBackupDir();
- destDir.mkdirs();
- if (!destDir.isDirectory()) {
- Log.e(TAG,
- String.format("Error creating backup directory '%s'.",
- destDir.getPath()));
- return false;
- }
-
- final File destFile = new File(destDir, getBackupFilename());
- destFilePath = destFile.getPath();
-
- try {
- FileChannel src = new FileInputStream(srcFile).getChannel();
- FileChannel dest = new FileOutputStream(destFile).getChannel();
-
- dest.transferFrom(src, 0, src.size());
-
- Log.i(TAG, String.format("Library backed up successfully: '%s'.",
- destFile.getPath()));
-
- return true;
-
- } catch (IOException e) {
- Log.e(TAG, String.format("Error backing up library: %s",
- e.toString()));
-
- return false;
- }
- }
-
- @Override
- protected void onPostExecute(Boolean success) {
- if (success) {
- final String message =
- context.getString(R.string.back_up_success_message,
- destFilePath);
- Toast.makeText(context, message, Toast.LENGTH_LONG).show();
- } else {
- Toast.makeText(context,
- R.string.back_up_error_message,
- Toast.LENGTH_SHORT).show();
- }
- }
-
- private static String getBackupFilename() {
- final Date today = new Date();
- final DateFormat sdf =
- new SimpleDateFormat("yyyyMMdd_HHmm", Locale.US);
-
- return "episodes_" + sdf.format(today) + ".db";
- }
- }
-
- private static class RestoreTask
- extends AsyncTask
- {
- private final Context context;
-
- public RestoreTask(Context context) {
- this.context = context;
- }
-
- @Override
- protected Boolean doInBackground(String... filename) {
- final File srcFile = new File(filename[0]);
- final File destFile =
- context.getDatabasePath(DatabaseOpenHelper.getDbName());
-
- try {
- FileChannel src = new FileInputStream(srcFile).getChannel();
- FileChannel dest = new FileOutputStream(destFile).getChannel();
-
- dest.transferFrom(src, 0, src.size());
-
- Log.i(TAG, String.format("Library restored successfully.",
- destFile.getPath()));
-
- return true;
-
- } catch (IOException e) {
- Log.e(TAG, String.format("Error restoring library: %s",
- e.toString()));
- return false;
- }
- }
-
- @Override
- protected void onPostExecute(Boolean success) {
- if (success) {
- Toast.makeText(context,
- R.string.restore_success_message,
- Toast.LENGTH_SHORT).show();
-
- ShowsProvider.reloadDatabase(context);
- } else {
- Toast.makeText(context,
- R.string.restore_error_message,
- Toast.LENGTH_SHORT).show();
- }
- }
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/db/DatabaseOpenHelper.java b/app/src/main/java/org/jamienicol/episodes/db/DatabaseOpenHelper.java
deleted file mode 100644
index 45f972e4..00000000
--- a/app/src/main/java/org/jamienicol/episodes/db/DatabaseOpenHelper.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes.db;
-
-import android.content.Context;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteOpenHelper;
-import android.util.Log;
-
-public class DatabaseOpenHelper extends SQLiteOpenHelper
-{
- private static final String name = "episodes.db";
- private static final int version = 5;
-
- private static final String TAG = "DatabaseOpenHelper";
-
- DatabaseOpenHelper(Context context) {
- super(context, name, null, version);
- }
-
- @Override
- public void onCreate(SQLiteDatabase db) {
- Log.d(TAG, "creating database");
- ShowsTable.onCreate(db);
- EpisodesTable.onCreate(db);
- }
-
- @Override
- public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- Log.d(TAG, String.format("upgrading database from version %d to %d",
- oldVersion, newVersion));
- ShowsTable.onUpgrade(db, oldVersion, newVersion);
- EpisodesTable.onUpgrade(db, oldVersion, newVersion);
- }
-
- @Override
- public void onOpen(SQLiteDatabase db) {
- Log.d(TAG, "opening database.");
- }
-
- public static String getDbName() {
- return name;
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/db/EpisodesTable.java b/app/src/main/java/org/jamienicol/episodes/db/EpisodesTable.java
deleted file mode 100644
index 9498d482..00000000
--- a/app/src/main/java/org/jamienicol/episodes/db/EpisodesTable.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes.db;
-
-import android.database.sqlite.SQLiteDatabase;
-import android.provider.BaseColumns;
-import android.util.Log;
-
-public class EpisodesTable
-{
- private static final String TAG = "EpisodesTable";
-
- public static final String TABLE_NAME = "episodes";
-
- public static final String COLUMN_ID = BaseColumns._ID;
- public static final String COLUMN_TVDB_ID = "tvdb_id";
- public static final String COLUMN_SHOW_ID = "show_id";
- public static final String COLUMN_NAME = "name";
- public static final String COLUMN_OVERVIEW = "overview";
- public static final String COLUMN_EPISODE_NUMBER = "episode_number";
- public static final String COLUMN_SEASON_NUMBER = "season_number";
- public static final String COLUMN_FIRST_AIRED = "first_aired";
- public static final String COLUMN_WATCHED = "watched";
-
- public static void onCreate(SQLiteDatabase db) {
- String create =
- String.format("CREATE TABLE %s (" +
- " %s INTEGER PRIMARY KEY," +
- " %s INTEGER UNIQUE NOT NULL," +
- " %s INTEGER NOT NULL," +
- " %s VARCHAR(200) NOT NULL," +
- " %s TEXT," +
- " %s INTEGER," +
- " %s INTEGER," +
- " %s DATE," +
- " %s BOOLEAN" +
- ");",
- TABLE_NAME,
- COLUMN_ID,
- COLUMN_TVDB_ID,
- COLUMN_SHOW_ID,
- COLUMN_NAME,
- COLUMN_OVERVIEW,
- COLUMN_EPISODE_NUMBER,
- COLUMN_SEASON_NUMBER,
- COLUMN_FIRST_AIRED,
- COLUMN_WATCHED);
-
- Log.d(TAG, String.format("creating episodes table: %s", create));
-
- db.execSQL(create);
- }
-
- public static void onUpgrade(SQLiteDatabase db,
- int oldVersion,
- int newVersion) {
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/db/ShowsProvider.java b/app/src/main/java/org/jamienicol/episodes/db/ShowsProvider.java
deleted file mode 100644
index 84d74b30..00000000
--- a/app/src/main/java/org/jamienicol/episodes/db/ShowsProvider.java
+++ /dev/null
@@ -1,336 +0,0 @@
-/*
- * Copyright (C) 2012 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes.db;
-
-import android.content.ContentProvider;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.UriMatcher;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.database.sqlite.SQLiteConstraintException;
-import android.database.sqlite.SQLiteDatabase;
-import android.database.sqlite.SQLiteQueryBuilder;
-import android.net.Uri;
-import android.util.Log;
-import org.jamienicol.episodes.BuildConfig;
-
-public class ShowsProvider extends ContentProvider
-{
- private static final String TAG = "ShowsProvider";
-
- private static final String URI_AUTHORITY =
- BuildConfig.APPLICATION_ID + ".db.ShowsProvider";
-
- private static final Uri CONTENT_URI_BASE =
- Uri.parse(ContentResolver.SCHEME_CONTENT +
- "://" +
- ShowsProvider.URI_AUTHORITY);
-
- public static final Uri CONTENT_URI_SHOWS =
- Uri.parse(ContentResolver.SCHEME_CONTENT +
- "://" +
- ShowsProvider.URI_AUTHORITY +
- "/" +
- ShowsTable.TABLE_NAME);
-
- public static final Uri CONTENT_URI_EPISODES =
- Uri.parse(ContentResolver.SCHEME_CONTENT +
- "://" +
- ShowsProvider.URI_AUTHORITY +
- "/" +
- EpisodesTable.TABLE_NAME);
-
- public static final String CONTENT_TYPE_SHOW_DIR =
- ContentResolver.CURSOR_DIR_BASE_TYPE + "/show";
- public static final String CONTENT_TYPE_SHOW_ITEM =
- ContentResolver.CURSOR_ITEM_BASE_TYPE + "/show";
- public static final String CONTENT_TYPE_EPISODE_DIR =
- ContentResolver.CURSOR_DIR_BASE_TYPE + "/episode";
- public static final String CONTENT_TYPE_EPISODE_ITEM =
- ContentResolver.CURSOR_ITEM_BASE_TYPE + "/episode";
-
- private static final int URI_TYPE_SHOWS = 1;
- private static final int URI_TYPE_SHOWS_ID = 2;
- private static final int URI_TYPE_EPISODES = 3;
- private static final int URI_TYPE_EPISODES_ID = 4;
-
- private static final UriMatcher uriMatcher =
- new UriMatcher(UriMatcher.NO_MATCH);
- static {
- uriMatcher.addURI(URI_AUTHORITY,
- ShowsTable.TABLE_NAME,
- URI_TYPE_SHOWS);
- uriMatcher.addURI(URI_AUTHORITY,
- ShowsTable.TABLE_NAME + "/#",
- URI_TYPE_SHOWS_ID);
- uriMatcher.addURI(URI_AUTHORITY,
- EpisodesTable.TABLE_NAME,
- URI_TYPE_EPISODES);
- uriMatcher.addURI(URI_AUTHORITY,
- EpisodesTable.TABLE_NAME + "/#",
- URI_TYPE_EPISODES_ID);
- }
-
- private DatabaseOpenHelper databaseOpenHelper;
-
- @Override
- public Cursor query(Uri uri,
- String[] projection,
- String selection,
- String[] selectionArgs,
- String sortOrder) {
- String table;
- String sel;
-
- switch (uriMatcher.match(uri)) {
- case URI_TYPE_SHOWS:
- table = ShowsTable.TABLE_NAME;
- sel = selection;
- break;
-
- case URI_TYPE_SHOWS_ID:
- table = ShowsTable.TABLE_NAME;
- sel = String.format("%s=%s",
- ShowsTable.COLUMN_ID,
- uri.getLastPathSegment());
- if (selection != null) {
- sel += " AND " + selection;
- }
- break;
-
- case URI_TYPE_EPISODES:
- table = EpisodesTable.TABLE_NAME;
- sel = selection;
- break;
-
- case URI_TYPE_EPISODES_ID:
- table = EpisodesTable.TABLE_NAME;
- sel = String.format("%s=%s",
- EpisodesTable.COLUMN_ID,
- uri.getLastPathSegment());
- if (selection != null) {
- sel += " AND " + selection;
- }
- break;
-
- default:
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- SQLiteDatabase db = databaseOpenHelper.getReadableDatabase();
- Cursor cursor = db.query(table,
- projection,
- sel,
- selectionArgs,
- null,
- null,
- sortOrder,
- null);
-
- cursor.setNotificationUri(getContext().getContentResolver(), uri);
-
- return cursor;
- }
-
- @Override
- public Uri insert(Uri uri, ContentValues values) {
-
- String table;
- Uri contentUri;
- if (uriMatcher.match(uri) == URI_TYPE_SHOWS) {
- table = ShowsTable.TABLE_NAME;
- contentUri = CONTENT_URI_SHOWS;
- } else if (uriMatcher.match(uri) == URI_TYPE_EPISODES) {
- table = EpisodesTable.TABLE_NAME;
- contentUri = CONTENT_URI_EPISODES;
- } else {
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- SQLiteDatabase db = databaseOpenHelper.getWritableDatabase();
- try {
- long rowId = db.insertOrThrow(table, null, values);
- Log.i(TAG, String.format("succesfully inserted row. id: %d",
- rowId));
- Uri rowUri = ContentUris.withAppendedId(contentUri,
- rowId);
- getContext().getContentResolver().notifyChange(rowUri, null);
-
- return rowUri;
- } catch (SQLiteConstraintException e) {
- Log.i(TAG, String.format("constraint error inserting row: %s",
- e.toString()));
- return null;
- }
- }
-
- @Override
- public int delete(Uri uri,
- String selection,
- String[] selectionArgs) {
- String table;
- String sel;
-
- switch (uriMatcher.match(uri)) {
- case URI_TYPE_SHOWS:
- table = ShowsTable.TABLE_NAME;
- sel = selection;
- break;
-
- case URI_TYPE_SHOWS_ID:
- table = ShowsTable.TABLE_NAME;
- sel = String.format("%s=%s",
- ShowsTable.COLUMN_ID,
- uri.getLastPathSegment());
- if (selection != null) {
- sel += " AND " + selection;
- }
-
- break;
-
- case URI_TYPE_EPISODES:
- table = EpisodesTable.TABLE_NAME;
- sel = selection;
- break;
-
- case URI_TYPE_EPISODES_ID:
- table = EpisodesTable.TABLE_NAME;
- sel = String.format("%s=%s",
- EpisodesTable.COLUMN_ID,
- uri.getLastPathSegment());
- if (selection != null) {
- sel += " AND " + selection;
- }
-
- break;
-
- default:
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- SQLiteDatabase db = databaseOpenHelper.getWritableDatabase();
- int count = db.delete(table,
- sel,
- selectionArgs);
-
- getContext().getContentResolver().notifyChange(uri, null);
-
- return count;
- }
-
- @Override
- public int update(Uri uri,
- ContentValues values,
- String selection,
- String[] selectionArgs) {
- String table;
- String sel;
-
- switch (uriMatcher.match(uri)) {
- case URI_TYPE_SHOWS:
- table = ShowsTable.TABLE_NAME;
- sel = selection;
- break;
-
- case URI_TYPE_SHOWS_ID:
- table = ShowsTable.TABLE_NAME;
- sel = String.format("%s=%s",
- ShowsTable.COLUMN_ID,
- uri.getLastPathSegment());
- if (selection != null) {
- sel += " AND " + selection;
- }
- break;
-
- case URI_TYPE_EPISODES:
- table = EpisodesTable.TABLE_NAME;
- sel = selection;
- break;
-
- case URI_TYPE_EPISODES_ID:
- table = EpisodesTable.TABLE_NAME;
- sel = String.format("%s=%s",
- EpisodesTable.COLUMN_ID,
- uri.getLastPathSegment());
- if (selection != null) {
- sel += " AND " + selection;
- }
- break;
-
- default:
- throw new IllegalArgumentException("Unknown URI " + uri);
- }
-
- SQLiteDatabase db = databaseOpenHelper.getWritableDatabase();
- int count = db.update(table,
- values,
- sel,
- selectionArgs);
-
- getContext().getContentResolver().notifyChange(uri, null);
-
- return count;
- }
-
- @Override
- public String getType(Uri uri) {
- switch (uriMatcher.match(uri)) {
- case URI_TYPE_SHOWS:
- return CONTENT_TYPE_SHOW_DIR;
-
- case URI_TYPE_SHOWS_ID:
- return CONTENT_TYPE_SHOW_ITEM;
-
- case URI_TYPE_EPISODES:
- return CONTENT_TYPE_EPISODE_DIR;
-
- case URI_TYPE_EPISODES_ID:
- return CONTENT_TYPE_EPISODE_ITEM;
-
- default:
- return null;
- }
- }
-
- @Override
- public boolean onCreate() {
- databaseOpenHelper = new DatabaseOpenHelper(getContext());
-
- return true;
- }
-
- public static void reloadDatabase(Context context) {
- final ContentResolver resolver = context.getContentResolver();
- final ContentProviderClient client =
- resolver.acquireContentProviderClient(URI_AUTHORITY);
- final ShowsProvider provider =
- (ShowsProvider)client.getLocalContentProvider();
-
- provider.databaseOpenHelper.close();
- provider.databaseOpenHelper =
- new DatabaseOpenHelper(provider.getContext());
-
- resolver.notifyChange(CONTENT_URI_BASE, null);
-
- client.release();
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/db/ShowsTable.java b/app/src/main/java/org/jamienicol/episodes/db/ShowsTable.java
deleted file mode 100644
index a9b73764..00000000
--- a/app/src/main/java/org/jamienicol/episodes/db/ShowsTable.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes.db;
-
-import android.database.sqlite.SQLiteDatabase;
-import android.provider.BaseColumns;
-import android.util.Log;
-
-public class ShowsTable
-{
- private static final String TAG = "ShowsTable";
-
- public static final String TABLE_NAME = "shows";
-
- public static final String COLUMN_ID = BaseColumns._ID;
- public static final String COLUMN_TVDB_ID = "tvdb_id";
- public static final String COLUMN_NAME = "name";
- public static final String COLUMN_OVERVIEW = "overview";
- public static final String COLUMN_FIRST_AIRED = "first_aired";
- public static final String COLUMN_STARRED = "starred";
- public static final String COLUMN_BANNER_PATH = "banner_path";
- public static final String COLUMN_FANART_PATH = "fanart_path";
- public static final String COLUMN_POSTER_PATH = "poster_path";
- public static final String COLUMN_NOTES = "notes";
-
- public static final String COLUMN_TYPE_ID = "INTEGER PRIMARY KEY";
- public static final String COLUMN_TYPE_TVDB_ID = "INTEGER UNIQUE NOT NULL";
- public static final String COLUMN_TYPE_NAME = "TEXT NOT NULL";
- public static final String COLUMN_TYPE_OVERVIEW = "TEXT";
- public static final String COLUMN_TYPE_FIRST_AIRED = "DATE";
- public static final String COLUMN_TYPE_STARRED = "BOOLEAN DEFAULT 0";
- public static final String COLUMN_TYPE_BANNER_PATH = "TEXT";
- public static final String COLUMN_TYPE_FANART_PATH = "TEXT";
- public static final String COLUMN_TYPE_POSTER_PATH = "TEXT";
- public static final String COLUMN_TYPE_NOTES = "TEXT";
-
- public static void onCreate(SQLiteDatabase db) {
- String create =
- String.format("CREATE TABLE %s (" +
- " %s %s," +
- " %s %s," +
- " %s %s," +
- " %s %s," +
- " %s %s," +
- " %s %s," +
- " %s %s," +
- " %s %s," +
- " %s %s," +
- " %s %s" +
- ");",
- TABLE_NAME,
- COLUMN_ID, COLUMN_TYPE_ID,
- COLUMN_TVDB_ID, COLUMN_TYPE_TVDB_ID,
- COLUMN_NAME, COLUMN_TYPE_NAME,
- COLUMN_OVERVIEW, COLUMN_TYPE_OVERVIEW,
- COLUMN_FIRST_AIRED, COLUMN_TYPE_FIRST_AIRED,
- COLUMN_STARRED, COLUMN_TYPE_STARRED,
- COLUMN_BANNER_PATH, COLUMN_TYPE_BANNER_PATH,
- COLUMN_FANART_PATH, COLUMN_TYPE_FANART_PATH,
- COLUMN_POSTER_PATH, COLUMN_TYPE_POSTER_PATH,
- COLUMN_NOTES, COLUMN_TYPE_NOTES);
-
- Log.d(TAG, String.format("creating shows table: %s", create));
-
- db.execSQL(create);
- }
-
- public static void onUpgrade(SQLiteDatabase db,
- int oldVersion,
- int newVersion) {
- switch (oldVersion) {
- case 1:
- // Add starred column
- Log.d(TAG, "upgrading shows table: adding starred column");
- db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
- TABLE_NAME,
- COLUMN_STARRED,
- COLUMN_TYPE_STARRED));
-
- // fall through
- case 2:
- // Add banner path column
- Log.d(TAG, "upgrading shows table: adding banner path column");
- db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
- TABLE_NAME,
- COLUMN_BANNER_PATH,
- COLUMN_TYPE_BANNER_PATH));
-
- // fall through
- case 3:
- // Add fanart path and poster path columns
- Log.d(TAG, "upgrading shows table: adding fanart path column");
- db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
- TABLE_NAME,
- COLUMN_FANART_PATH,
- COLUMN_TYPE_FANART_PATH));
-
- Log.d(TAG, "upgrading shows table: adding poster path column");
- db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
- TABLE_NAME,
- COLUMN_POSTER_PATH,
- COLUMN_TYPE_POSTER_PATH));
-
- // fall through
- case 4:
- // Add notes column
- Log.d(TAG, "upgrading shows table: adding notes column");
- db.execSQL(String.format("ALTER TABLE %s ADD COLUMN %s %s",
- TABLE_NAME,
- COLUMN_NOTES,
- COLUMN_TYPE_NOTES));
- // fall through
- }
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/services/AddShowService.java b/app/src/main/java/org/jamienicol/episodes/services/AddShowService.java
deleted file mode 100644
index e68f6e39..00000000
--- a/app/src/main/java/org/jamienicol/episodes/services/AddShowService.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes.services;
-
-import android.app.IntentService;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Handler;
-import android.util.Log;
-import android.widget.Toast;
-import org.jamienicol.episodes.db.EpisodesTable;
-import org.jamienicol.episodes.db.ShowsTable;
-import org.jamienicol.episodes.db.ShowsProvider;
-import org.jamienicol.episodes.tvdb.Client;
-import org.jamienicol.episodes.tvdb.Episode;
-import org.jamienicol.episodes.tvdb.Show;
-import org.jamienicol.episodes.R;
-
-public class AddShowService extends IntentService
-{
- private static final String TAG = "AddShowService";
-
- private Handler handler;
-
- public AddShowService() {
- super("AddShowService");
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- handler = new Handler();
-
- return super.onStartCommand(intent, flags, startId);
- }
-
- @Override
- protected void onHandleIntent(Intent intent) {
- final Client tvdbClient = new Client("25B864A8BC56AFAD");
-
- final int tvdbId = intent.getIntExtra("tvdbId", 0);
- final String showName = intent.getStringExtra("showName");
-
- if (isShowAlreadyAdded(tvdbId) == false) {
-
- showMessage(getString(R.string.adding_show, showName));
-
- // fetch full show + episode information from tvdb
- final Show show = tvdbClient.getShow(tvdbId);
-
- if (show != null) {
- // add show and episodes to database
- final int showId = insertShow(show);
- for (Episode episode : show.getEpisodes()) {
- insertEpisode(episode, showId);
- }
-
- showMessage(getString(R.string.show_added, showName));
- } else {
- showMessage(getString(R.string.error_adding_show, showName));
- }
- } else {
- showMessage(getString(R.string.show_already_added, showName));
- }
- }
-
- private boolean isShowAlreadyAdded(int tvdbId) {
- final String[] projection = {
- };
- final String selection = String.format("%s=?",
- ShowsTable.COLUMN_TVDB_ID);
- final String[] selectionArgs = {
- Integer.valueOf(tvdbId).toString()
- };
- final Cursor cursor =
- getContentResolver().query(ShowsProvider.CONTENT_URI_SHOWS,
- projection,
- selection,
- selectionArgs,
- null);
-
- return cursor.moveToFirst();
- }
-
- private int insertShow(Show show) {
- // fill in information about the show
- final ContentValues showValues = new ContentValues();
- showValues.put(ShowsTable.COLUMN_TVDB_ID, show.getId());
- showValues.put(ShowsTable.COLUMN_NAME, show.getName());
- showValues.put(ShowsTable.COLUMN_OVERVIEW, show.getOverview());
- if (show.getFirstAired() != null) {
- showValues.put(ShowsTable.COLUMN_FIRST_AIRED,
- show.getFirstAired().getTime() / 1000);
- }
- showValues.put(ShowsTable.COLUMN_BANNER_PATH, show.getBannerPath());
- showValues.put(ShowsTable.COLUMN_FANART_PATH, show.getFanartPath());
- showValues.put(ShowsTable.COLUMN_POSTER_PATH, show.getPosterPath());
-
- // insert the show into the database
- final Uri showUri =
- getContentResolver().insert(ShowsProvider.CONTENT_URI_SHOWS,
- showValues);
-
- // need to obtain the ID of the inserted show.
- // the ID is just the final segment of the URI
- final int showId = Integer.parseInt(showUri.getLastPathSegment());
-
- Log.i(TAG, String.format("show %s successfully added to database as row %d. adding episodes",
- show.getName(),
- showId));
-
- return showId;
- }
-
- private void insertEpisode(Episode episode, int showId) {
- final ContentValues episodeValues = new ContentValues();
- episodeValues.put(EpisodesTable.COLUMN_TVDB_ID, episode.getId());
- episodeValues.put(EpisodesTable.COLUMN_SHOW_ID, showId);
- episodeValues.put(EpisodesTable.COLUMN_NAME, episode.getName());
- episodeValues.put(EpisodesTable.COLUMN_OVERVIEW,
- episode.getOverview());
- episodeValues.put(EpisodesTable.COLUMN_EPISODE_NUMBER,
- episode.getEpisodeNumber());
- episodeValues.put(EpisodesTable.COLUMN_SEASON_NUMBER,
- episode.getSeasonNumber());
- if (episode.getFirstAired() != null) {
- episodeValues.put(EpisodesTable.COLUMN_FIRST_AIRED,
- episode.getFirstAired().getTime() / 1000);
- }
-
- getContentResolver().insert(ShowsProvider.CONTENT_URI_EPISODES,
- episodeValues);
- }
-
- private void showMessage(String message) {
- final Context context = this;
- final String text = message;
- final int duration = Toast.LENGTH_SHORT;
-
- handler.post(new Runnable() {
- @Override
- public void run() {
- final Toast toast = Toast.makeText(context, text, duration);
- toast.show();
- }
- });
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/tvdb/Client.java b/app/src/main/java/org/jamienicol/episodes/tvdb/Client.java
deleted file mode 100644
index 3d0612a4..00000000
--- a/app/src/main/java/org/jamienicol/episodes/tvdb/Client.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes.tvdb;
-
-import android.util.Log;
-import com.squareup.okhttp.OkHttpClient;
-import com.squareup.okhttp.Request;
-import com.squareup.okhttp.Response;
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.util.List;
-import java.util.Locale;
-import org.jamienicol.episodes.EpisodesApplication;
-
-public class Client
-{
- private static final String TAG = Client.class.getName();
- private static final String baseUrl = "http://thetvdb.com/api";
-
- private final String apiKey;
- private final OkHttpClient http;
-
- public Client(String apiKey) {
- this.apiKey = apiKey;
- http = EpisodesApplication.getInstance().getHttpClient();
- }
-
- public List searchShows(String query) {
-
- try {
- final String escapedQuery = URLEncoder.encode(query, "UTF-8");
- final String url = String.format("%s/GetSeries.php?seriesname=%s",
- baseUrl,
- escapedQuery);
- Log.d(TAG, String.format("Sending request to %s", url));
-
- final Request request = new Request.Builder().url(url).build();
-
- final Response response = http.newCall(request).execute();
-
- Log.d(TAG, String.format("Received response %d: %s",
- response.code(),
- response.message()));
-
- if (response.isSuccessful()) {
- final SearchShowsParser parser = new SearchShowsParser();
-
- return parser.parse(response.body().byteStream());
- } else {
- return null;
- }
- } catch (IOException e) {
- Log.w(TAG, e);
- return null;
- }
- }
-
- public Show getShow(int id) {
- try {
- final String url = String.format(Locale.US,
- "%s/%s/series/%d/all/en.xml",
- baseUrl,
- apiKey,
- id);
- Log.d(TAG, String.format("Sending request to %s", url));
-
- final Request request = new Request.Builder().url(url).build();
-
- final Response response = http.newCall(request).execute();
-
- Log.d(TAG, String.format("Received response %d: %s",
- response.code(),
- response.message()));
-
- if (response.isSuccessful()) {
- final GetShowParser parser = new GetShowParser();
-
- return parser.parse(response.body().byteStream());
- } else {
- return null;
- }
- } catch (IOException e) {
- Log.w(TAG, e);
- return null;
- }
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/tvdb/GetShowParser.java b/app/src/main/java/org/jamienicol/episodes/tvdb/GetShowParser.java
deleted file mode 100644
index ae92e790..00000000
--- a/app/src/main/java/org/jamienicol/episodes/tvdb/GetShowParser.java
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Copyright (C) 2012-2014 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes.tvdb;
-
-import android.sax.Element;
-import android.sax.EndElementListener;
-import android.sax.EndTextElementListener;
-import android.sax.RootElement;
-import android.sax.StartElementListener;
-import android.util.Log;
-import java.io.InputStream;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.XMLReaderFactory;
-
-class GetShowParser
-{
- private static final String TAG = "GetShowParser";
-
- // show which is being parsed
- Show show;
-
- // episode which is currently being parsed
- Episode episode;
-
- public Show parse(InputStream inputStream) {
-
- try {
- InputSource inputSource = new InputSource(inputStream);
- XMLReader xmlReader = XMLReaderFactory.createXMLReader("org.xmlpull.v1.sax2.Driver");
-
- RootElement rootElement = new RootElement("Data");
-
- Element seriesElement = rootElement.requireChild("Series");
-
- Element idElement = seriesElement.requireChild("id");
- idElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- int id = Integer.parseInt(body);
-
- Log.i(TAG, String.format("Parsed show ID: %d", id));
- show.setId(id);
- }
- });
-
- Element nameElement = seriesElement.requireChild("SeriesName");
- nameElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- Log.i(TAG, String.format("Parsed show name: %s", body));
- show.setName(body);
- }
- });
-
- Element overviewElement = seriesElement.getChild("Overview");
- overviewElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- Log.i(TAG,
- String.format("Parsed show overview: %s", body));
- show.setOverview(body);
- }
- });
-
- Element firstAiredElement = seriesElement.getChild("FirstAired");
- firstAiredElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- try {
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd",
- Locale.US);
- Date firstAired = df.parse(body);
-
- Log.i(TAG,
- String.format("Parsed show first aired date: %s",
- firstAired.toString()));
- show.setFirstAired(firstAired);
-
- } catch (ParseException e) {
- Log.w(TAG, "Error parsing first aired date: " + e.toString());
- show.setFirstAired(null);
- }
- }
- });
-
- Element bannerPathElement = seriesElement.getChild("banner");
- bannerPathElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- Log.i(TAG,
- String.format("Parsed show banner path: %s", body));
- show.setBannerPath(body);
- }
- });
-
- final Element fanartPathElement = seriesElement.getChild("fanart");
- fanartPathElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- Log.i(TAG,
- String.format("Parsed show fanart path: %s", body));
- show.setFanartPath(body);
- }
- });
-
- final Element posterPathElement = seriesElement.getChild("poster");
- posterPathElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- Log.i(TAG,
- String.format("Parsed show poster path: %s", body));
- show.setPosterPath(body);
- }
- });
-
- Element episodeElement = rootElement.getChild("Episode");
- episodeElement.setStartElementListener(new StartElementListener() {
- public void start(Attributes attributes) {
- Log.i(TAG, "Begin parsing episode");
- episode = new Episode();
- }
- });
- episodeElement.setEndElementListener(new EndElementListener() {
- public void end() {
- Log.i(TAG, "End parsing episode");
- show.getEpisodes().add(episode);
- episode = null;
- }
- });
-
- Element episodeIdElement = episodeElement.requireChild("id");
- episodeIdElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- int id = Integer.parseInt(body);
-
- Log.i(TAG, String.format("Parsed episode ID: %d", id));
- episode.setId(id);
- }
- });
-
- Element episodeNameElement = episodeElement.getChild("EpisodeName");
- episodeNameElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- Log.i(TAG, String.format("Parsed episode name: %s", body));
- episode.setName(body);
- }
- });
-
- Element episodeOverviewElement = episodeElement.getChild("Overview");
- episodeOverviewElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- Log.i(TAG,
- String.format("Parsed episode overview: %s", body));
- episode.setOverview(body);
- }
- });
-
- Element episodeEpisodeNumberElement = episodeElement.getChild("EpisodeNumber");
- episodeEpisodeNumberElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- int episodeNumber = Integer.parseInt(body);
-
- Log.i(TAG,
- String.format("Parsed episode episode number: %d",
- episodeNumber));
- episode.setEpisodeNumber(episodeNumber);
- }
- });
-
- Element episodeSeasonNumberElement = episodeElement.getChild("SeasonNumber");
- episodeSeasonNumberElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- int seasonNumber = Integer.parseInt(body);
-
- Log.i(TAG,
- String.format("Parsed episode season number: %d",
- seasonNumber));
- episode.setSeasonNumber(seasonNumber);
- }
- });
-
- Element episodeFirstAiredElement = episodeElement.getChild("FirstAired");
- episodeFirstAiredElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- try {
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd",
- Locale.US);
- Date firstAired = df.parse(body);
-
- Log.i(TAG,
- String.format("Parsed episode first aired date: %s",
- firstAired.toString()));
- episode.setFirstAired(firstAired);
-
- } catch (ParseException e) {
- Log.w(TAG, "Error parsing first aired date: " + e.toString());
- episode.setFirstAired(null);
- }
- }
- });
-
- xmlReader.setContentHandler(rootElement.getContentHandler());
-
- show = new Show();
- show.setEpisodes(new LinkedList());
- xmlReader.parse(inputSource);
-
- return show;
-
- } catch (SAXException e) {
- Log.w(TAG, "SAXException - parse: " + e.toString());
- return null;
- } catch (IOException e) {
- Log.w(TAG, "IOException - parse: " + e.toString());
- return null;
- }
- }
-}
diff --git a/app/src/main/java/org/jamienicol/episodes/tvdb/SearchShowsParser.java b/app/src/main/java/org/jamienicol/episodes/tvdb/SearchShowsParser.java
deleted file mode 100644
index 0bac4dce..00000000
--- a/app/src/main/java/org/jamienicol/episodes/tvdb/SearchShowsParser.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2012 Jamie Nicol
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package org.jamienicol.episodes.tvdb;
-
-import android.sax.Element;
-import android.sax.EndElementListener;
-import android.sax.EndTextElementListener;
-import android.sax.RootElement;
-import android.sax.StartElementListener;
-import android.util.Log;
-import java.io.InputStream;
-import java.io.IOException;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Locale;
-import org.xml.sax.Attributes;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.XMLReader;
-import org.xml.sax.helpers.XMLReaderFactory;
-
-class SearchShowsParser
-{
- private static final String TAG = "SearchShowsParser";
-
- // show which is currently being parsed
- Show current;
-
- // shows which have finished being parsed
- List parsed;
-
- public List parse(InputStream inputStream) {
-
- try {
- InputSource inputSource = new InputSource(inputStream);
- XMLReader xmlReader = XMLReaderFactory.createXMLReader("org.xmlpull.v1.sax2.Driver");
-
- RootElement rootElement = new RootElement("Data");
- Element seriesElement = rootElement.getChild("Series");
- seriesElement.setStartElementListener(new StartElementListener() {
- public void start(Attributes attributes) {
- Log.i(TAG, "Begin parsing show");
- current = new Show();
- }
- });
- seriesElement.setEndElementListener(new EndElementListener() {
- public void end() {
- Log.i(TAG, "End parsing show");
- parsed.add(current);
- current = null;
- }
- });
-
- Element idElement = seriesElement.requireChild("id");
- idElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- int id = Integer.parseInt(body);
-
- Log.i(TAG, String.format("Parsed ID: %d", id));
- current.setId(id);
- }
- });
-
- Element nameElement = seriesElement.requireChild("SeriesName");
- nameElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- Log.i(TAG, String.format("Parsed name: %s", body));
- current.setName(body);
- }
- });
-
- Element overviewElement = seriesElement.getChild("Overview");
- overviewElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- Log.i(TAG, String.format("Parsed overview: %s", body));
- current.setOverview(body);
- }
- });
-
- Element firstAiredElement = seriesElement.getChild("FirstAired");
- firstAiredElement.setEndTextElementListener(new EndTextElementListener() {
- public void end(String body) {
- try {
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd",
- Locale.US);
- Date firstAired = df.parse(body);
-
- Log.i(TAG, String.format("Parsed first aired date: %s",
- firstAired.toString()));
- current.setFirstAired(firstAired);
-
- } catch (ParseException e) {
- Log.w(TAG, "Error parsing first aired date: " + e.toString());
- current.setFirstAired(null);
- }
- }
- });
-
- xmlReader.setContentHandler(rootElement.getContentHandler());
-
- current = null;
- parsed = new LinkedList();
- xmlReader.parse(inputSource);
-
- return parsed;
-
- } catch (SAXException e) {
- Log.w(TAG, "SAXException - parse: " + e.toString());
- return null;
- } catch (IOException e) {
- Log.w(TAG, "IOException - parse: " + e.toString());
- return null;
- }
- }
-}
diff --git a/app/src/main/res/drawable-hdpi/ic_show_starred.png b/app/src/main/res/drawable-hdpi/ic_show_starred.png
deleted file mode 100644
index 86674448..00000000
Binary files a/app/src/main/res/drawable-hdpi/ic_show_starred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/ic_show_unstarred.png b/app/src/main/res/drawable-hdpi/ic_show_unstarred.png
deleted file mode 100644
index 0fefae71..00000000
Binary files a/app/src/main/res/drawable-hdpi/ic_show_unstarred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/ic_shows_list_starred.png b/app/src/main/res/drawable-hdpi/ic_shows_list_starred.png
deleted file mode 100644
index a593e6a7..00000000
Binary files a/app/src/main/res/drawable-hdpi/ic_shows_list_starred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/ic_shows_list_unstarred.png b/app/src/main/res/drawable-hdpi/ic_shows_list_unstarred.png
deleted file mode 100644
index f6670329..00000000
Binary files a/app/src/main/res/drawable-hdpi/ic_shows_list_unstarred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-hdpi/shows_list_gradient.xml b/app/src/main/res/drawable-hdpi/shows_list_gradient.xml
new file mode 100644
index 00000000..398c2358
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/shows_list_gradient.xml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/app/src/main/res/drawable-mdpi/ic_show_starred.png b/app/src/main/res/drawable-mdpi/ic_show_starred.png
deleted file mode 100644
index 603691e9..00000000
Binary files a/app/src/main/res/drawable-mdpi/ic_show_starred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_show_unstarred.png b/app/src/main/res/drawable-mdpi/ic_show_unstarred.png
deleted file mode 100644
index f4609b98..00000000
Binary files a/app/src/main/res/drawable-mdpi/ic_show_unstarred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_shows_list_starred.png b/app/src/main/res/drawable-mdpi/ic_shows_list_starred.png
deleted file mode 100644
index b5638942..00000000
Binary files a/app/src/main/res/drawable-mdpi/ic_shows_list_starred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/ic_shows_list_unstarred.png b/app/src/main/res/drawable-mdpi/ic_shows_list_unstarred.png
deleted file mode 100644
index 2e6d1e50..00000000
Binary files a/app/src/main/res/drawable-mdpi/ic_shows_list_unstarred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_show_starred.png b/app/src/main/res/drawable-xhdpi/ic_show_starred.png
deleted file mode 100644
index 4d24afe7..00000000
Binary files a/app/src/main/res/drawable-xhdpi/ic_show_starred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_show_unstarred.png b/app/src/main/res/drawable-xhdpi/ic_show_unstarred.png
deleted file mode 100644
index ad3b4efe..00000000
Binary files a/app/src/main/res/drawable-xhdpi/ic_show_unstarred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_shows_list_starred.png b/app/src/main/res/drawable-xhdpi/ic_shows_list_starred.png
deleted file mode 100644
index 2a8273e7..00000000
Binary files a/app/src/main/res/drawable-xhdpi/ic_shows_list_starred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_shows_list_unstarred.png b/app/src/main/res/drawable-xhdpi/ic_shows_list_unstarred.png
deleted file mode 100644
index 0f91e22e..00000000
Binary files a/app/src/main/res/drawable-xhdpi/ic_shows_list_unstarred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_show_starred.png b/app/src/main/res/drawable-xxhdpi/ic_show_starred.png
deleted file mode 100644
index f516d813..00000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_show_starred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_show_unstarred.png b/app/src/main/res/drawable-xxhdpi/ic_show_unstarred.png
deleted file mode 100644
index 2efab925..00000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_show_unstarred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_shows_list_starred.png b/app/src/main/res/drawable-xxhdpi/ic_shows_list_starred.png
deleted file mode 100644
index 4395ee39..00000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_shows_list_starred.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_shows_list_unstarred.png b/app/src/main/res/drawable-xxhdpi/ic_shows_list_unstarred.png
deleted file mode 100644
index 2603f8f5..00000000
Binary files a/app/src/main/res/drawable-xxhdpi/ic_shows_list_unstarred.png and /dev/null differ
diff --git a/app/src/main/res/drawable/archived_toggle.xml b/app/src/main/res/drawable/archived_toggle.xml
new file mode 100644
index 00000000..fea721ea
--- /dev/null
+++ b/app/src/main/res/drawable/archived_toggle.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_show_archived.xml b/app/src/main/res/drawable/ic_show_archived.xml
new file mode 100644
index 00000000..f18c6a1b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_show_archived.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_show_starred.xml b/app/src/main/res/drawable/ic_show_starred.xml
new file mode 100644
index 00000000..3c3df63c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_show_starred.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_show_unarchived.xml b/app/src/main/res/drawable/ic_show_unarchived.xml
new file mode 100644
index 00000000..9f89aa54
--- /dev/null
+++ b/app/src/main/res/drawable/ic_show_unarchived.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_show_unstarred.xml b/app/src/main/res/drawable/ic_show_unstarred.xml
new file mode 100644
index 00000000..88dd5a3c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_show_unstarred.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/starred_toggle.xml b/app/src/main/res/drawable/starred_toggle.xml
index 00eb4ba6..82ea142f 100644
--- a/app/src/main/res/drawable/starred_toggle.xml
+++ b/app/src/main/res/drawable/starred_toggle.xml
@@ -3,8 +3,8 @@
xmlns:android="http://schemas.android.com/apk/res/android" >
+ android:drawable="@drawable/ic_show_starred" />
+ android:drawable="@drawable/ic_show_unstarred" />
diff --git a/app/src/main/res/layout/about_activity.xml b/app/src/main/res/layout/about_activity.xml
index bf804d72..92695331 100644
--- a/app/src/main/res/layout/about_activity.xml
+++ b/app/src/main/res/layout/about_activity.xml
@@ -34,7 +34,14 @@
+
diff --git a/app/src/main/res/layout/add_show_search_activity.xml b/app/src/main/res/layout/add_show_search_activity.xml
index 4e411c1a..95bba716 100644
--- a/app/src/main/res/layout/add_show_search_activity.xml
+++ b/app/src/main/res/layout/add_show_search_activity.xml
@@ -3,7 +3,27 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="match_parent" >
+ android:layout_height="match_parent">
+
+
+
+
+
-
diff --git a/app/src/main/res/layout/episode_details_fragment.xml b/app/src/main/res/layout/episode_details_fragment.xml
index c36770d2..f4f9e5a1 100644
--- a/app/src/main/res/layout/episode_details_fragment.xml
+++ b/app/src/main/res/layout/episode_details_fragment.xml
@@ -1,5 +1,5 @@
-
-
+
diff --git a/app/src/main/res/layout/main_activity.xml b/app/src/main/res/layout/main_activity.xml
index 9607d557..33a7518b 100644
--- a/app/src/main/res/layout/main_activity.xml
+++ b/app/src/main/res/layout/main_activity.xml
@@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent" >
diff --git a/app/src/main/res/layout/seasons_list_fragment.xml b/app/src/main/res/layout/seasons_list_fragment.xml
index a843a9f5..baf2ecc6 100644
--- a/app/src/main/res/layout/seasons_list_fragment.xml
+++ b/app/src/main/res/layout/seasons_list_fragment.xml
@@ -1,5 +1,5 @@
-
+ android:layout_height="match_parent"
+ android:id="@+id/settings_layout">
diff --git a/app/src/main/res/layout/show_activity.xml b/app/src/main/res/layout/show_activity.xml
index 87db6ed7..67109e84 100644
--- a/app/src/main/res/layout/show_activity.xml
+++ b/app/src/main/res/layout/show_activity.xml
@@ -1,17 +1,17 @@
-
-
-
-
-
+
-
-
+
-
-
+
diff --git a/app/src/main/res/layout/show_details_fragment.xml b/app/src/main/res/layout/show_details_fragment.xml
index b4fe18bd..7649e9c5 100644
--- a/app/src/main/res/layout/show_details_fragment.xml
+++ b/app/src/main/res/layout/show_details_fragment.xml
@@ -1,5 +1,5 @@
-
@@ -20,4 +20,4 @@
android:textAppearance="?android:attr/textAppearance"
android:padding="8dp"/>
-
+
diff --git a/app/src/main/res/layout/show_notes_fragment.xml b/app/src/main/res/layout/show_notes_fragment.xml
index 520ac494..33f957e1 100644
--- a/app/src/main/res/layout/show_notes_fragment.xml
+++ b/app/src/main/res/layout/show_notes_fragment.xml
@@ -1,5 +1,5 @@
-
@@ -11,4 +11,4 @@
android:autoLink="all"
android:gravity="top"
android:padding="4dp"/>
-
+
diff --git a/app/src/main/res/layout/shows_list_fragment.xml b/app/src/main/res/layout/shows_list_fragment.xml
index a9f0473a..a6514dcb 100644
--- a/app/src/main/res/layout/shows_list_fragment.xml
+++ b/app/src/main/res/layout/shows_list_fragment.xml
@@ -8,8 +8,7 @@
android:id="@id/android:list"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:padding="2dp"
- android:clipToPadding="false"
+ android:clipToPadding="true"
android:divider="@null"
android:scrollbarStyle="outsideOverlay" />
diff --git a/app/src/main/res/layout/shows_list_item.xml b/app/src/main/res/layout/shows_list_item.xml
index c03a31af..c298ab3a 100644
--- a/app/src/main/res/layout/shows_list_item.xml
+++ b/app/src/main/res/layout/shows_list_item.xml
@@ -2,25 +2,37 @@
+ android:paddingLeft="-1dp"
+ android:paddingRight="-1dp">
-
+
+
+
+
+ android:layout_below="@id/name_banner_frame">
diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml
index 28845e46..709f7bb8 100644
--- a/app/src/main/res/menu/main.xml
+++ b/app/src/main/res/menu/main.xml
@@ -6,8 +6,8 @@
android:id="@+id/menu_add_new_show"
android:icon="@drawable/ic_menu_add_show"
android:title="@string/menu_add_new_show"
- episodes:showAsAction="ifRoom|withText|collapseActionView"
- episodes:actionViewClass="android.support.v7.widget.SearchView" />
+ episodes:showAsAction="ifRoom|collapseActionView"
+ episodes:actionViewClass="androidx.appcompat.widget.SearchView" />
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100755
index 00000000..e2c420d6
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
old mode 100644
new mode 100755
index ff10861e..5f9ed3e3
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png
new file mode 100755
index 00000000..0a673ba4
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..90722c92
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
old mode 100644
new mode 100755
index afc48238..a12cde89
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png
new file mode 100755
index 00000000..afd4c770
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..fd92fee8
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
old mode 100644
new mode 100755
index 834b1025..4be4a27e
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png
new file mode 100755
index 00000000..e4a51344
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..5d7626d1
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
old mode 100644
new mode 100755
index a998b1bf..e2aa383b
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png
new file mode 100755
index 00000000..78bf8153
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..d86cb12f
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 00000000..90ead600
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100755
index 00000000..713157c5
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100755
index 00000000..af231731
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
new file mode 100644
index 00000000..74542c2a
--- /dev/null
+++ b/app/src/main/res/values-bg/strings.xml
@@ -0,0 +1,63 @@
+
+
+ Филтърът показва
+ Звезден
+ Относно
+ Изтриване на шоуто
+ Епизоди
+ Премахване на звезди
+ Звездна спектакция
+ Настройки
+ Възстановяване от архивиране
+ Обновяване на шоуто
+ Опресняване на всички предавания
+ Маркирай показването като гледано
+ Маркирай като неподредено
+ Марк сезон, както беше наблюдаван
+ Маркиране на сезон като необгледан
+ Предстоящи
+ Ход
+ Архивирани
+ Всички
+ Добавяне на показване към библиотеката
+ Добавяне на ново шоу към библиотеката
+ Търсене на ново шоу
+ За
+ Първо излъчено: %s
+ Не може да се добави %s в библиотеката
+ Архивирането е завършено: \'%s\"
+ Не може да се архивира библиотеката
+ Версия %s (Къмит на %s)
+ Интерфейс
+ Серия за автоматично обновяване
+ Уведомяване при синхронизиране показва
+ Показване на синхронизирането
+ %1$d от %2$d епизоди, наблюдавани
+ Гледани
+ (+ %d предстоящи)
+ Преглед
+ Бележки
+ Следващата
+ %s вече в библиотека
+ %s добавен към библиотеката
+ Настройки
+ Специални
+ Сезон %d
+ "%1$02dx%2$02d - "
+ Библиотеката е възстановена
+ Не може да се възстанови библиотеката
+ Възстановяване от архив
+ Не са намерени архиви в папка \'%s\'.
+ Показва най-новия сезон
+ Обратен ред на сортиране
+ Език
+ Само при неомерени връзки
+ Автоматично обновяване
+ Интервал
+ Премахни показването от архива
+ Архивно шоу
+ Епизодите са софтуер за GPLv3+. Моля, пиши bugs, писане на код, превеждайте или направете всичко друго, което можете да направите, за да помогнете!
+ Следете кои епизоди сте гледали от любимите си предавания
+ Архивиране на библиотека
+ Добавяне %s към библиотеката
+
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
new file mode 100644
index 00000000..dc6699f2
--- /dev/null
+++ b/app/src/main/res/values-cs/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Sledujte, které epizody jste sledovali z vašich oblíbených pořadů
+ Lägg till %s i biblioteket
+
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
new file mode 100644
index 00000000..e04987fd
--- /dev/null
+++ b/app/src/main/res/values-de/strings.xml
@@ -0,0 +1,63 @@
+
+
+ Verfolge, welche Episoden Deiner Lieblingssendungen Du gesehen hast
+ Benutzeroberfläche
+ Serien automatisch aktualisieren
+ %1$d von %2$d Folgen gesehen
+ Gesehen
+ (+ %d angekündigt)
+ Übersicht
+ Nächste
+ Folgen
+ %s bereits in der Bibliothek
+ %s zur Bibliothek hinzugefügt
+ Einstellungen
+ Specials
+ Staffel %d
+ "%1$02dx%2$02d - "
+ Bibliothek wiederhergestellt
+ Bibliothek konnte nicht wiederhergestellt werden
+ Aus einer Sicherung wiederherstellen
+ Keine Datensicherungen im Ordner „%s“ gefunden
+ Neueste Staffel zuerst
+ Reihenfolge umkehren
+ Sprache
+ Nur bei nicht gemessenen Verbindungen
+ Automatisch aktualisieren
+ Intervall
+ Serie aus Archiv entfernen
+ Serie archivieren
+ Serie aus Favoriten entfernen
+ Serie favorisieren
+ Einstellungen
+ Datensicherung wiederherstellen
+ Serie aktualisieren
+ Serien aktualisieren
+ Folge als gesehen markieren
+ Folge als ungesehen markieren
+ Staffel als gesehen markieren
+ Staffel als ungesehen markieren
+ Angekündigte
+ Angefangene
+ Archivierte
+ Favoriten
+ Alle
+ Serien filtern
+ Serie löschen
+ Datensicherung erstellen
+ Serie hinzufügen
+ Serie hinzufügen
+ Neue Serie suchen
+ Über
+ Erstausstrahlung: %s
+ %s konnte nicht zur Bibliothek hinzugefügt werden
+ Datensicherung fertiggestellt: „%s“
+ Datensicherung der Bibliothek konnte nicht erstellt werden
+ %s wird zur Bibliothek hinzugefügt
+ Über
+ Episodes ist freie Software unter GPLv3+. Hilf gern mit, indem Du Fehler meldest, Code oder Übersetzungen schreibst!
+ Synchronisierung anzeigen
+ Benachrichtigung beim Synchronisieren von Sendungen
+ Anmerkungen
+ Version %s (Commit %s)
+
\ No newline at end of file
diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml
new file mode 100644
index 00000000..33f2a923
--- /dev/null
+++ b/app/src/main/res/values-eo/strings.xml
@@ -0,0 +1,63 @@
+
+
+ Fasado
+ %1$d el %2$d epizodoj spektiĝis
+ Spektita
+ Superrigardo
+ Notoj
+ Epizodoj
+ %s jam en biblioteko
+ %s aldonita al biblioteko
+ Agordoj
+ Sezono %d
+ Biblioteko restaŭriĝis
+ Ne povis restaŭri bibliotekon
+ Restaŭri savkopion
+ Neniu savkopio troviĝis en la dosierujo \'%s\'.
+ Lingvo
+ Elarkivigi spektaĵon
+ Enarkivigi spektaĵon
+ Malmarki kiel plej ŝatatan
+ Marki kiel plej ŝatatan
+ Agordoj
+ Marki spektaĵon kiel spektitan
+ Marki spektaĵon kiel nespektitan
+ Marki sezonon kiel spektita
+ Marki sezonon kiel nespektita
+ En arkivo
+ Plej ŝatataj
+ Ĉiuj
+ Forigi spektaĵon
+ Savkopii bibliotekon
+ Aldoni spektaĵon al biblioteko
+ Aldoni novan spektaĵon al biblioteko
+ Serĉi novan spektaĵon
+ Pri
+ Ne povis aldoni %s al biblioteko
+ Savkopio kompleta: \'%s\'
+ Ne povis savkopii bibliotekon
+ Aldonante %s al biblioteko
+ Pri
+ Filtri spektaĵojn
+ Versio %s (Enmeto %s)
+ Specialaĵoj
+ "%1$02d×%2$02d - "
+ Intervalo
+ Prezentata
+ Prezentota
+ (+ %d prezentota(j))
+ Unua Prezentado: %s
+ Sciigo pri sinkronigo de spektaĵoj
+ Reŝargi spektaĵon
+ Reŝargi ĉiujn spektaĵojn
+ Episodes estas kopilasita libera programo sub la permesiloj GPLv3+. Bonvolu raporti cimojn, skribi kodojn, traduki aŭ helpi ĉiel ajn!
+ Marku la spektitajn epizodojn de viaj plej ŝatataj spektaĵoj
+ Aŭtomate reŝargi spektaĵon
+ Sekva
+ Sciigi pri sinkronigo
+ Nur per nemezurataj retkonektoj
+ Aŭtomate reŝargata
+ Montri unue la plej novan sezonon
+ Mala ordo
+ Restaŭri savkopion
+
\ No newline at end of file
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
new file mode 100644
index 00000000..f9a15986
--- /dev/null
+++ b/app/src/main/res/values-es/strings.xml
@@ -0,0 +1,63 @@
+
+
+ (+ %d próximamente)
+ Descripción
+ Sólo en conexiones no medidas
+ Próximamente
+ En emisión
+ Emitida por primera vez: %s
+ Episodes es software libre copyleft bajo licencia GPLv3+. ¡Por favor registra bugs, escribe código, traduce o haz cualquier cosa que puedas para ayudar!
+ Mantén un registro de qué episodios de tus series favoritas has visto
+ Versión %s (Commit %s)
+ Interfaz
+ Actualizar series automáticamente
+ Notificación cuando se sincronizan series
+ Sincronizar serie
+ %1$d de %2$d episodios vistos
+ Visto
+ Notas
+ Siguiente
+ Episodios
+ %s ya está en la biblioteca
+ %s añadida a la biblioteca
+ Ajustes
+ Especiales
+ Temporada %d
+ "%1$02dx%2$02d - "
+ Biblioteca restaurada
+ No se ha podido restaurar la biblioteca
+ Restaurar desde copia de seguridad
+ No se han encontrado copias de seguridad en la carpeta \'%s\'.
+ Muestra primero la temporada más nueva
+ Orden inverso
+ Lenguaje
+ Actualización automática activada
+ Intervalo
+ Quitar serie del archivo
+ Archivar serie
+ No destacar serie
+ Destacar serie
+ Ajustes
+ Restaurar desde copia de seguridad
+ Actualizar serie
+ Actualizar todas las series
+ Marcar serie como vista
+ Marcar serie como no vista
+ Marcar temporada como vista
+ Marcar temporada como no vista
+ Archivadas
+ Destacadas
+ Todas
+ Filtrar series
+ Borrar serie
+ Realizar copia de seguridad de la biblioteca
+ Añadir serie a la biblioteca
+ Añadir nueva serie a la biblioteca
+ Buscar una nueva serie
+ Acerca de
+ No se ha podido añadir %s a la biblioteca
+ Copia de seguridad completada: \'%s\'
+ No se ha podido realizar la copia de seguridad de la biblioteca
+ Añadiendo %s a la biblioteca
+ Acerca de
+
\ No newline at end of file
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
new file mode 100644
index 00000000..34d0f397
--- /dev/null
+++ b/app/src/main/res/values-eu/strings.xml
@@ -0,0 +1,63 @@
+
+
+ Bertsioa %s (Egin %s)
+ Interfazea
+ Automatikoki freskatu serieak
+ Ikuskizunak sinkronizatzean jakinarazpena
+ Erakutsi sinkronizazioa
+ %1$d of %2$d ikusitako pasarteak
+ Ikusita
+ (+ %d datozenak)
+ Ikuspegi orokorra
+ Oharrak
+ Hurrengoa
+ Episodes
+ %s dagoeneko liburutegian
+ %s liburutegian gehitu da
+ Ezarpenak
+ Bereziak
+ Denboraldia %d
+ "%1$02dx%2$02d - "
+ Liburutegia zaharberritu da
+ Ezin izan da liburutegia leheneratu
+ Leheneratu segurtasun kopiatik
+ Ez da babeskopiarik aurkitu \'%s\' karpetan.
+ Denboraldi berriena erakusten du lehenengo
+ Alderantziz ordenatzeko ordena
+ Hizkuntza
+ Meditu gabeko konexioetan soilik
+ Freskatze automatikoa aktibatuta
+ Tartea
+ Kendu ikuskizuna artxibotik
+ Artxibo ikuskizuna
+ Erakutsi izarra
+ Izar ikuskizuna
+ Ezarpenak
+ Leheneratu segurtasun kopiatik
+ Freskatu ikuskizuna
+ Freskatu ikuskizun guztiak
+ Markatu ikuskizuna ikusi bezala
+ Markatu ikuskizuna ikusi gabeko moduan
+ Markatu denboraldia ikusitako moduan
+ Markatu denboraldia ikusi gabeko moduan
+ Datozenak
+ Martxan
+ Artxibatuta
+ Izarrak
+ Guztiak
+ Iragazkien ikuskizunak
+ Ezabatu ikuskizuna
+ Egin liburutegiaren babeskopia
+ Gehitu ikuskizuna liburutegian
+ Gehitu ikuskizun berria liburutegian
+ Bilatu ikuskizun berria
+ Buruz
+ Lehenengo igorpena: %s
+ Ezin izan da %s gehitu liburutegian
+ Babeskopia osatua: \'%s\'
+ Ezin izan da liburutegiaren segurtasun kopia egin
+ liburutegian %s gehitzen
+ Buruz
+ Episodes copylefted libre software lizentziadun GPLv3 + da. Mesedez, bidali akatsak, idatzi kodea, itzuli edo egin ezazu laguntzeko duzun guztia!
+ Jarrai itzazu zure saio gogokoenen atalak ikusi dituzunak
+
\ No newline at end of file
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
new file mode 100644
index 00000000..d113f502
--- /dev/null
+++ b/app/src/main/res/values-fr/strings.xml
@@ -0,0 +1,63 @@
+
+
+ À propos
+ Première diffusion : %s
+ À propos
+ Ajout de %s à la bibliothèque
+ Episodes est un logiciel libre sous licence GPLv3+. Vous pouvez signaler des erreurs, écrire du code, traduire ou faire quoi que ce soit d\'autre pour contribuer !
+ Gardez une trace des épisodes que vous avez regardés dans vos séries préférées
+ Ajouter la série à la bibliothèque
+ Ajouter une nouvelle série à la bibliothèque
+ Rechercher une nouvelle série
+ Impossible d\'ajouter %s à la bibliothèque
+ Sauvegarde terminée : « %s »
+ Impossible de sauvegarder la bibliothèque
+ Interface
+ Actualiser automatiquement les séries
+ Notification lors de la synchronisation des séries
+ Synchronisation de la série
+ %1$d épisode sur %2$d regardé
+ Regardé
+ (+ %d à venir)
+ Aperçu
+ Notes
+ Suivant
+ Épisodes
+ %s déjà dans la bibliothèque
+ %s ajouté à la bibliothèque
+ Paramètres
+ Spécial
+ Saison %d
+ "%1$02dx%2$02d - "
+ Bibliothèque restaurée
+ Impossible de restaurer la bibliothèque
+ Restaurer à partir d\'une sauvegarde
+ Aucune sauvegarde trouvée dans le dossier « %s ».
+ Affiche la dernière saison en premier
+ Inverser l\'ordre de tri
+ Langue
+ Seulement sur les connexions non limitées
+ Actualisation auto activée
+ Intervale
+ Retirer la série des archives
+ Archiver la série
+ Retirer la série des favoris
+ Ajouter la série aux favoris
+ Paramètres
+ Restaurer une sauvegarde
+ Actualiser la série
+ Actualiser toutes les séries
+ Marquer la série comme regardée
+ Marquer la série comme non regardée
+ Marquer la saison comme regardée
+ Marquer la saison comme non regardée
+ À venir
+ En cours
+ Archivé
+ Favoris
+ Tout
+ Filtrer les séries
+ Supprimer la série
+ Restaurer la bibliothèque
+ %s verzió (Commit %s)
+
\ No newline at end of file
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
new file mode 100644
index 00000000..36847e2b
--- /dev/null
+++ b/app/src/main/res/values-hr/strings.xml
@@ -0,0 +1,63 @@
+
+
+ Prikaži sinkronizaciju
+ Označi sezonu kao pogledanu
+ Filtriraj emisije
+ Odznači emisiju
+ Obnovi iz sigurnosne kopije
+ Sezona %d
+ Automatsko aktualiziranje uključeno
+ Označi emisiju
+ Označi emisiju kao pogledanu
+ Prvi put emitirano: %s
+ (+ %d predstojeće)
+ Spremi sigurnosnu kopiju biblioteke
+ Ukloni emisjiu iz arhive
+ Prati stanja gledanja epizoda tvojih omiljenih emisija
+ Emisija „%s” je dodana u biblioteku
+ Dodaj novu emisiju u biblioteku
+ Obrni redoslijed
+ Pogledano
+ Epizode
+ Obnovi iz sigurnosne kopije
+ Aktualiziraj emisiju
+ Predstojeće
+ Neuspjelo spremanje sigurnosne kopije biblioteke
+ „Epizode” je copyleft slobodni softver s licencom GPLv3+. Prijavi greške, programiraj, prevodi ili pomogni na bilo koji drugi način!
+ Pregled
+ Emisija „%s” se već nalazi u biblioteci
+ Prikazuje najnoviju sezonu na prvom mjestu
+ Arhivirane
+ Napomene
+ Sljedeća
+ Postavke
+ Neuspjelo obnavljanje biblioteke
+ Jezik
+ Traži novu emisiju
+ Informacije
+ Nadolazeće
+ Označi emisiju kao nepogledanu
+ Obavijesti pri sinkronizaciji emisija
+ Aktualiziraj sve emisije
+ Informacije
+ Pogledanih epizoda: %1$d od %2$d
+ Biblioteka obnovljena
+ Postavke
+ Označene
+ Označi sezonu kao nepogledanu
+ Izbriši emisiju
+ Samo na vezama bez ograničenja
+ Interval
+ Arhiviraj emisiju
+ Sučelje
+ Emisija „%s” se dodaje u biblioteku
+ Neuspjelo dodavanje emisije „%s” u biblioteku
+ Sve
+ Dodaj emisiju u biblioteku
+ Automatsko aktualiziranje serija
+ Specijalne
+ Sigurnosna kopija gotova: „%s”
+ U mapi „%s” nema sigurnosnih kopija.
+ "%1$02d × %2$02d - "
+ Verzija %s (Izmjena %s)
+
\ No newline at end of file
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
new file mode 100644
index 00000000..3fefd3df
--- /dev/null
+++ b/app/src/main/res/values-it/strings.xml
@@ -0,0 +1,63 @@
+
+
+ Aggiunta di %s alla libreria
+ Informazioni
+ Episodes è un software libero con copyleft di licenza GPLv3+. Per favore, segnalate errori, scrivete codice, traducete o fate qualsiasi altra cosa per aiutare!
+ Tieni traccia di quali episodi hai guardato dei tuoi programmi preferiti
+ Versione %s (Commit %s)
+ Interfaccia
+ Aggiornamento automatico serie
+ Notifica quando le serie si sincronizzano
+ Sincronizzazione della serie
+ %1$d di %2$d episodi guardati
+ Guardato
+ (+ %d prossimamente)
+ Panoramica
+ Note
+ Successivo
+ Episodes
+ %s già nella raccolta
+ %s aggiunto alla raccolta
+ Impostazioni
+ Speciali
+ Stagione %d
+ "%1$02dx%2$02d - "
+ Raccolta ripristinata
+ Non è stato possibile ripristinare la raccolta
+ Ripristina da un backup
+ Nessun backup trovato nella cartella «%s».
+ Visualizza prima la stagione più recente
+ Invertisci l\'ordine di ordinamento
+ Lingua
+ Solo con connessioni non misurate
+ Aggiornamento automatico abilitato
+ Periodicità
+ Rimuovi la serie dall\'archivio
+ Archivia la serie
+ Rimuovi la serie dai favoriti
+ Aggiungi la serie ai favoriti
+ Impostazioni
+ Ripristino da backup
+ Aggiorna la serie
+ Aggiorna tutte le serie
+ Segna la serie come guardata
+ Segna come non guardato
+ Segna la stagione come guardato
+ Segna la stagione come non guardato
+ Prossimamente
+ In corso
+ Archiviato
+ Stellato
+ Tutto
+ Filtra le serie
+ Elimina la serie
+ Backup della raccolta
+ Aggiungi la serie alla raccolta
+ Aggiungi una nuova serie alla raccolta
+ Cerca una nuova serie
+ Informazioni
+ Prima messa in onda: %s
+ Impossibile aggiungere %s alla raccolta
+ Backup completato: «%s»
+ Impossibile eseguire il backup della raccolta
+
\ No newline at end of file
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
new file mode 100644
index 00000000..e51e86fc
--- /dev/null
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -0,0 +1,63 @@
+
+
+ Grensesnitt
+ Auto-gjenoppfrisk serier
+ Merknad ved synkronisering av programmer
+ Vis synkronisering
+ %1$d av %2$d episoder sett
+ Sett
+ (+ %d kommende)
+ Oversikt
+ Notater
+ Neste
+ Episoder
+ %s allerede i bibliotek
+ %s lagt til i bibliotek
+ Innstillinger
+ Sesong %d
+ Spesialsendinger
+ "%1$02dx%2$02d - "
+ Bibliotek gjenopprettet
+ Klarte ikke å gjenopprette bibliotek
+ Gjenopprett fra sikkehetskopi
+ Fant ingen sikkerhetskopier i «%s»-mappen.
+ Vis nyeste sesong først
+ Reverser sorteringsrekkefølge
+ Språk
+ Kun på ubegrensede tilkoblinger
+ Auto-gjenoppfrisking påskrudd
+ Intervall
+ Fjern program fra arkiv
+ Arkiver program
+ Fjern stjernemerking
+ Stjernemerk program
+ Innstillinger
+ Gjenopprett fra sikkerhetskopi
+ Gjenoppfrisk program
+ Gjenoppfrisk alle programmer
+ Marker program som sett
+ Marker program som usett
+ Marker sesong som sett
+ Marker sesong som usett
+ Kommende
+ Underveis
+ Arkivert
+ Stjernemerkede
+ Alle
+ Filtrer programmer
+ Slett program
+ Sikkerhetskopier bibliotek
+ Legg til program i bibliotek
+ Legg til nytt program i bibliotek
+ Søk etter nytt program
+ Om
+ Først sendt: %s
+ Klarte ikke å legge til %s i biblioteket
+ Sikkerhetskopi utført: «%s»
+ Klarte ikke å sikkerhetskopiere bibliotek
+ Legger til %s i biblioteket
+ Om
+ Episodes er gemenlig fri programvare lisensiert GPLv3+. Send inn feil, skriv kode, oversett og gjør hva du kan for å hjelpe til.
+ Hold orden på hvilke episoder du har sett av dine favoritt-programmer
+ Versjon %s (innsendelse %s)
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 00000000..9dbe58a8
--- /dev/null
+++ b/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,63 @@
+
+
+ Интерфейс
+ Автообновление серий
+ Уведомление при синхронизации шоу
+ Показать синхронизацию
+ %1$d из %2$d эпизодов просмотрено
+ Просмотрено
+ (+ %d предстоящие)
+ Обзор
+ Примечания
+ Следующий
+ Эпизоды
+ %s уже в библиотеке
+ %s добавлен в библиотеку
+ Настройки
+ Specials
+ Сезон %d
+ "%1$02dx%2$02d - "
+ Библиотека восстановлена
+ Не удалось восстановить библиотеку
+ Восстановить из резервной копии
+ В папке \'%s\' нет резервных копий.
+ Отображать сначала новейший сезон
+ Обратный порядок сортировки
+ Язык
+ Только на безлимитных соединениях
+ Автоматическое обновление
+ Интервал
+ Удалить из избранного
+ Удалить шоу из архива
+ Добавить в архив
+ Избранные
+ Добавить в избранное
+ Настройки
+ Восстановить из резервной копии
+ Обновить шоу
+ Обновить все шоу
+ Отметить шоу как просмотренное
+ Отметить шоу как непросмотренное
+ Отметить сезон как просмотренный
+ Отметить сезон как непросмотренный
+ Предстоящие
+ В процессе
+ Архивные
+ Все
+ Фильтр шоу
+ Удалить шоу
+ Резервное копирование библиотеки
+ Добавить шоу в библиотеку
+ Добавить новое шоу в библиотеку
+ Поиск нового шоу
+ О приложении
+ Первый эфир: %s
+ Не удалось добавить %s в библиотеку
+ Резервное копирование завершено: \'%s\'
+ Не удалось создать резервную копию библиотеки
+ Добавление %s в библиотеку
+ О приложении
+ Episodes - это бесплатное программное обеспечение с авторским левом под лицензией GPLv3 +. Пожалуйста, сообщайте об ошибках, пишите код, переводите или делайте что-нибудь ещё, чтобы помочь!
+ Следите за тем, какие серии любимых шоу вы смотрели
+ Версия %s (Изменение %s)
+
\ No newline at end of file
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
new file mode 100644
index 00000000..4c859348
--- /dev/null
+++ b/app/src/main/res/values-sv/strings.xml
@@ -0,0 +1,63 @@
+
+
+ Version %s (åtagande %s)
+ Gränssnitt
+ %1$d av %2$d avsnitt sedda
+ "%1$02dx%2$02d - "
+ Avisering när program synkroniseras
+ Programsynkronisering
+ Sedda
+ Överblick
+ Anteckningar
+ Nästa
+ Avsnitt
+ %s redan i biblioteket
+ %s lades till i biblioteket
+ Inställningar
+ Säsong %d
+ Biblioteket återställdes
+ Kunde inte återställa bibliotek
+ Återställ från säkerhetskopia
+ Inga säkerhetskopior hittades i mappen \'%s\'.
+ Visar den nyaste säsongen först
+ Omvänd sorteringsordning
+ Språk
+ Endast på obegränsade anslutningar
+ Automatisk uppdatering på
+ Intervall
+ Ta bort program från arkiv
+ Arkivera program
+ Sluta stjärnmarkera program
+ Stjärnmarkera program
+ Inställningar
+ Återställ från säkerhetskopia
+ Uppdatera program
+ Uppdatera alla program
+ Kommande
+ Pågående
+ Arkiverade
+ Stjärnmärkta
+ Alla
+ Filtrera program
+ Ta bort program
+ Säkerhetskopiera biblioteket
+ Lägg till program i biblioteket
+ Lägg till program i biblioteket
+ Sök efter nytt program
+ Om
+ Sändes först: %s
+ Kunde inte lägga till %s i biblioteket
+ Säkerhetskopieringen färdig: \'%s\'
+ Kunde inte säkerhetskopiera biblioteket
+ Lägger till %s i biblioteket
+ Om
+ (+ %d kommande)
+ Håll koll på vilka avsnitt du har tittat på av dina favoritprogram
+ Episodes är copylefted libre software licensierad GPLv3+. Var snäll och skicka in felrapporter, skriv kod, översätt eller gör vad du kan för att hjälpa till!
+ Markera säsong som osedd
+ Markera säsong som sedd
+ Markera program som osett
+ Markera program som sett
+ Specialavsnitt
+ Automatisk uppdatering av serier
+
\ No newline at end of file
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
new file mode 100644
index 00000000..0cc73f69
--- /dev/null
+++ b/app/src/main/res/values-tr/strings.xml
@@ -0,0 +1,63 @@
+
+
+ "%1$02dx%2$02d - "
+ Sürüm %s (Kaydetme %s)
+ Arayüz
+ Dizileri otomatik yenile
+ Şovları senkronize ederken bildirim
+ Senkronizasyonu göster
+ %2$d bölümden %1$d görüldü
+ İzlendi
+ (+%d yakında)
+ Genel Bakış
+ Notlar
+ Sıradaki
+ Bölümler
+ %s zaten kitaplıkta
+ %s kitaplığa eklendi
+ Ayarlar
+ Özel
+ Sezon %d
+ Kitaplık geri yüklendi
+ Kitaplık geri yüklenemedi
+ Yedekten geri yükle
+ \'%s\' klasöründe yedek bulunamadı.
+ Önce en yeni sezonu görüntüler
+ Ters sıralama düzeni
+ Dil
+ Yalnızca ölçülmemiş bağlantılarda
+ Otomatik yenileme açık
+ Aralık
+ Diziyi arşivden kaldır
+ Diziyi arşivle
+ Gösterinin yıldızını kaldır
+ Gösteriyi yıldızla
+ Ayarlar
+ Yedekten geri yükle
+ Diziyi yenile
+ Tüm dizileri yenile
+ Diziyi izlenmiş olarak işaretle
+ Diziyi izlenmemiş olarak işaretle
+ Sezonu izlendi olarak işaretle
+ Sezonu izlenmemiş olarak işaretle
+ Yakında
+ Yolda
+ Arşivlendi
+ Yıldızlı
+ Hepsi
+ Filtrele
+ Diziyi sil
+ Kitaplığı yedekle
+ Kitaplığa dizi ekle
+ Kitaplığa yeni dizi ekle
+ Yeni dizi ara
+ Hakkında
+ İlk Yayınlanma Tarihi: %s
+ %s kitaplığa eklenemedi
+ Yedekleme tamamlandı: \'%s\'
+ Kitaplık yedeklenemedi
+ %s kitaplığa ekleniyor
+ Hakkında
+ Episodes, GPLv3+ lisanslı copyleft özgür yazılımdır. Lütfen hataları bildirin, kod yazın, tercüme edin veya yardım etmek için elinizden gelen her şeyi yapın!
+ En sevdiğiniz dizilerin hangi bölümlerini izlediğinizi takip edin
+
\ No newline at end of file
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 8eec19ea..c90e7af3 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -12,4 +12,54 @@
- 168
- 336
+
+ - Chinese
+ - Croation
+ - Czech
+ - Dansk
+ - Deutsch
+ - English
+ - Español
+ - Français
+ - Greek
+ - Hebrew
+ - Italiano
+ - Japanese
+ - Korean
+ - Magyar
+ - Nederlands
+ - Norsk
+ - Polski
+ - Portuguese
+ - Russian
+ - Slovenian
+ - Suomeksi
+ - Svenska
+ - Turkish
+
+
+ - zh
+ - hr
+ - cs
+ - da
+ - de
+ - en
+ - es
+ - fr
+ - el
+ - he
+ - it
+ - ja
+ - ko
+ - hu
+ - nl
+ - no
+ - pl
+ - pt
+ - ru
+ - sl
+ - fi
+ - sv
+ - tr
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 70ad0eda..8f47cfa1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,15 +1,15 @@
- Keep track of which episodes you\'ve watched of your favourite TV shows
- Episodes is free software licensed under the GNU GPL (version 3 or later). Please file bugs, write code, or do anything else you can to help!
+ Keep track of which episodes you\'ve watched of your favourite shows
+ Episodes is copylefted libre software licensed GPLv3+. Please file bugs, write code, translate or do anything else you can to help!
About
- TV show information is supplied by TheTVDB.com, and is licensed under the Creative Commons Attribution license. Please contribute information or artwork to TheTVDB.com if you can.
- http://github.com/jamienicol/episodes
- Episodes
+ This product uses the TMDB API but is not endorsed or certified by TMDB.
+ https://github.com/red-coracle/episodes
+ Episodes
Adding %s to library
- Error backing up library
- Back up complete: \'%s\'
- Error adding %s to library
+ Could not back up library
+ Back-up complete: \'%s\'
+ Could not add %s to library
First Aired: %s
About
Search for new show
@@ -17,13 +17,15 @@
Add show to library
Back up library
Delete show
- All shows
Filter shows
- Starred shows
- Uncompleted shows
- Mark season as not watched
+ All
+ Starred
+ Archived
+ Underway
+ Upcoming
+ Mark season as unwatched
Mark season as watched
- Mark show as not watched
+ Mark show as unwatched
Mark show as watched
Refresh all shows
Refresh show
@@ -31,24 +33,39 @@
Settings
Star show
Unstar show
- Refresh frequency
- Auto-refresh library
- Refresh only via Wi-Fi
- No backups found in folder \'%s\'.
+ Archive show
+ Remove show from archive
+ Interval
+ Auto-refresh on
+ Only on unmetered connections
+ Language
+ Reverse sorting order
+ Displays the newest season first
+ No backups found in \'%s\' folder.
Restore from backup
- Error restoring library
+ Could not restore library
Library restored
"%1$02dx%2$02d - "
Season %d
Specials
Settings
%s added to library
- %s is already in library
+ %s already in library
Episodes
Next
Notes
Overview
(+ %d upcoming)
Watched
- Watched %1$d of %2$d episodes
+ %1$d of %2$d episodes seen
+ Show sync
+ Notification when syncing shows
+ Auto-refresh series
+ Interface
+ Version %s (Commit %s)
+ No search results found
+ Metadata provider has switched to TMDB. We recommend backing up your database before refreshing all metadata. Press Continue to proceed with refreshing all shows or Cancel to return.
+ Metadata Provider Change
+ Continue
+ Cancel
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 2fc2c6ed..099e3ccf 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -1,21 +1,39 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 7130c34e..dc4fab06 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,14 +1,16 @@
buildscript {
repositories {
- jcenter()
+ mavenCentral()
+ google()
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.0.0'
+ classpath 'com.android.tools.build:gradle:7.3.0'
}
}
allprojects {
repositories {
- jcenter()
+ mavenCentral()
+ google()
}
}
diff --git a/fastlane/metadata/android/en-US/changelogs/0014.txt b/fastlane/metadata/android/en-US/changelogs/0014.txt
new file mode 100644
index 00000000..ca5b8a0e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0014.txt
@@ -0,0 +1,5 @@
+- Upgrade SDK and gradle versions
+- Switch from support library to androidx
+- Replace universal-image-loader with Glide
+- Use newer OkHttp & TVDB client versions
+- Fix sync notification/progress
diff --git a/fastlane/metadata/android/en-US/changelogs/0015.txt b/fastlane/metadata/android/en-US/changelogs/0015.txt
new file mode 100644
index 00000000..5155e7db
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0015.txt
@@ -0,0 +1,6 @@
+- Replace deprecated AsyncTask
+- Searching and adding shows now uses language preference
+- Use poster image if fanart image is unavailable
+- Display loading indicator when fetching fanart/poster image
+- Add preference categories and use switches instead of checkboxes
+- Update dependencies and gradle
diff --git a/fastlane/metadata/android/en-US/changelogs/0016.txt b/fastlane/metadata/android/en-US/changelogs/0016.txt
new file mode 100644
index 00000000..8a229bbd
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0016.txt
@@ -0,0 +1,4 @@
+- Add filter for upcoming shows
+- Fix app crash when searching for shows
+- Add language column to episodes table if it does not exist
+- Update dependencies/gradle
diff --git a/fastlane/metadata/android/en-US/changelogs/0017.txt b/fastlane/metadata/android/en-US/changelogs/0017.txt
new file mode 100644
index 00000000..6532813a
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0017.txt
@@ -0,0 +1,4 @@
+- Added translations for Croatian, French, German, Norwegian Bokmål, Russian, and Spanish. Many thanks to the contributors on Weblate!
+- Add version and commit info to the About screen
+- Use the configured language when downloading show data
+- Update dependencies/gradle
diff --git a/fastlane/metadata/android/en-US/changelogs/0018.txt b/fastlane/metadata/android/en-US/changelogs/0018.txt
new file mode 100644
index 00000000..3faf3084
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0018.txt
@@ -0,0 +1,2 @@
+- New and updated translations
+
diff --git a/fastlane/metadata/android/en-US/changelogs/0019.txt b/fastlane/metadata/android/en-US/changelogs/0019.txt
new file mode 100644
index 00000000..1da5869b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0019.txt
@@ -0,0 +1 @@
+- Fix searching for shows to add
diff --git a/fastlane/metadata/android/en-US/changelogs/0020.txt b/fastlane/metadata/android/en-US/changelogs/0020.txt
new file mode 100644
index 00000000..b78efb71
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0020.txt
@@ -0,0 +1,5 @@
+- Display a message when a search returns no results
+- Show a loading spinner when searching for new shows
+- When marking a season as watched, only mark aired episodes
+- Updated dependencies
+- Updated translations
diff --git a/fastlane/metadata/android/en-US/changelogs/0021.txt b/fastlane/metadata/android/en-US/changelogs/0021.txt
new file mode 100644
index 00000000..3c5fdd6c
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0021.txt
@@ -0,0 +1,3 @@
+- Fix back button on season details
+- Update R8 configuration
+- Fix crash on Android 12
diff --git a/fastlane/metadata/android/en-US/changelogs/0022.txt b/fastlane/metadata/android/en-US/changelogs/0022.txt
new file mode 100644
index 00000000..47224f0f
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0022.txt
@@ -0,0 +1,3 @@
+- Update badges in README
+- New launcher icon
+- Use storage framework on API19+
diff --git a/fastlane/metadata/android/en-US/changelogs/0023.txt b/fastlane/metadata/android/en-US/changelogs/0023.txt
new file mode 100644
index 00000000..b4df4144
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0023.txt
@@ -0,0 +1,5 @@
+- Final release with TVDB as metadata provider
+- Target version 33 (Android 13)
+- Raise minimum version to 21 (Android L)
+- Request notification permission on Android 13+
+- Update gradle version
diff --git a/fastlane/metadata/android/en-US/changelogs/0024.txt b/fastlane/metadata/android/en-US/changelogs/0024.txt
new file mode 100644
index 00000000..46cf4c5b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0024.txt
@@ -0,0 +1,4 @@
+- Switch to TMDB as metadata provider
+- Add support for themed app icons
+- Fix for null/zero TVDB ID
+- Update setup-java CI action
diff --git a/fastlane/metadata/android/en-US/changelogs/0025.txt b/fastlane/metadata/android/en-US/changelogs/0025.txt
new file mode 100644
index 00000000..8a0540f8
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/0025.txt
@@ -0,0 +1,2 @@
+- Fix sorting in shows list
+- Update star/archive icons to improve visibility
diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt
new file mode 100644
index 00000000..f60f6d49
--- /dev/null
+++ b/fastlane/metadata/android/en-US/full_description.txt
@@ -0,0 +1,4 @@
+Keep track of which episodes you’ve watched of your favourite TV shows.
+This product uses the TMDB API but is not endorsed or certified by TMDB.
+
+Anti-Feature: NonFreeNet - uses a non-free network service which is impossible, or not easy to replace without rebuilding the app
diff --git a/fastlane/metadata/android/en-US/images/icon.png b/fastlane/metadata/android/en-US/images/icon.png
new file mode 100755
index 00000000..cb8a520a
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/icon.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png
new file mode 100644
index 00000000..30b60b1f
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png
new file mode 100644
index 00000000..7cd07807
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png
new file mode 100644
index 00000000..ecbfc1e5
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
new file mode 100644
index 00000000..b3c1673d
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png
new file mode 100644
index 00000000..ccd8ec2d
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png
new file mode 100644
index 00000000..e3cca5b0
Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png differ
diff --git a/fastlane/metadata/android/en-US/short_description.txt b/fastlane/metadata/android/en-US/short_description.txt
new file mode 100644
index 00000000..d79ea21c
--- /dev/null
+++ b/fastlane/metadata/android/en-US/short_description.txt
@@ -0,0 +1 @@
+Keep track of which episodes you’ve watched of your favourite TV shows.
diff --git a/fastlane/metadata/android/en-US/title.txt b/fastlane/metadata/android/en-US/title.txt
new file mode 100644
index 00000000..c84917f3
--- /dev/null
+++ b/fastlane/metadata/android/en-US/title.txt
@@ -0,0 +1 @@
+Episodes
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 00000000..5465fec0
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,2 @@
+android.enableJetifier=true
+android.useAndroidX=true
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 8c0fb64a..cc4fdc29 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 0c71e760..f7189a77 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionSha256Sum=db9c8211ed63f61f60292c69e80d89196f9eb36665e369e7f00ac4cc841c2219
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/gradlew b/gradlew
index 91a7e269..2fe81a7d 100755
--- a/gradlew
+++ b/gradlew
@@ -1,4 +1,20 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
##############################################################################
##
@@ -6,20 +22,38 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
-warn ( ) {
+warn () {
echo "$*"
}
-die ( ) {
+die () {
echo
echo "$*"
echo
@@ -30,6 +64,7 @@ die ( ) {
cygwin=false
msys=false
darwin=false
+nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
@@ -40,31 +75,11 @@ case "`uname`" in
MINGW* )
msys=true
;;
+ NONSTOP* )
+ nonstop=true
+ ;;
esac
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
-APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
@@ -90,7 +105,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -110,10 +125,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
@@ -138,27 +154,30 @@ if $cygwin ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index aec99730..24467a14 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -8,14 +24,14 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +62,9 @@ echo location of your Java installation.
goto fail
:init
-@rem Get command-line arguments, handling Windowz variants
+@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,11 +75,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line