Delete core module (#7060)

This commit is contained in:
ByteHamster
2024-04-05 19:20:27 +02:00
committed by GitHub
parent 2143ab1351
commit 92ab575b15
104 changed files with 223 additions and 669 deletions

3
ui/chapters/README.md Normal file
View File

@ -0,0 +1,3 @@
# :ui:chapters
This module provides chapter loading and merging for display, but not the actual UI to display them.

21
ui/chapters/build.gradle Normal file
View File

@ -0,0 +1,21 @@
plugins {
id("com.android.library")
}
apply from: "../../common.gradle"
apply from: "../../playFlavor.gradle"
android {
namespace "de.danoeh.antennapod.ui.chapters"
}
dependencies {
implementation project(':model')
implementation project(':net:common')
implementation project(':parser:media')
implementation project(':parser:feed')
implementation project(':storage:database')
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
implementation "commons-io:commons-io:$commonsioVersion"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
}

View File

@ -0,0 +1,70 @@
package de.danoeh.antennapod.ui.chapters;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import de.danoeh.antennapod.model.feed.Chapter;
import java.util.List;
public class ChapterMerger {
private static final String TAG = "ChapterMerger";
private ChapterMerger() {
}
/**
* This method might modify the input data.
*/
@Nullable
public static List<Chapter> merge(@Nullable List<Chapter> chapters1, @Nullable List<Chapter> chapters2) {
Log.d(TAG, "Merging chapters");
if (chapters1 == null) {
return chapters2;
} else if (chapters2 == null) {
return chapters1;
} else if (chapters2.size() > chapters1.size()) {
return chapters2;
} else if (chapters2.size() < chapters1.size()) {
return chapters1;
} else {
// Merge chapter lists of same length. Store in chapters2 array.
// In case the lists can not be merged, return chapters1 array.
for (int i = 0; i < chapters2.size(); i++) {
Chapter chapterTarget = chapters2.get(i);
Chapter chapterOther = chapters1.get(i);
if (Math.abs(chapterTarget.getStart() - chapterOther.getStart()) > 1000) {
Log.e(TAG, "Chapter lists are too different. Cancelling merge.");
return score(chapters1) > score(chapters2) ? chapters1 : chapters2;
}
if (TextUtils.isEmpty(chapterTarget.getImageUrl())) {
chapterTarget.setImageUrl(chapterOther.getImageUrl());
}
if (TextUtils.isEmpty(chapterTarget.getLink())) {
chapterTarget.setLink(chapterOther.getLink());
}
if (TextUtils.isEmpty(chapterTarget.getTitle())) {
chapterTarget.setTitle(chapterOther.getTitle());
}
}
return chapters2;
}
}
/**
* Tries to give a score that can determine which list of chapters a user might want to see.
*/
private static int score(List<Chapter> chapters) {
int score = 0;
for (Chapter chapter : chapters) {
score = score
+ (TextUtils.isEmpty(chapter.getTitle()) ? 0 : 1)
+ (TextUtils.isEmpty(chapter.getLink()) ? 0 : 1)
+ (TextUtils.isEmpty(chapter.getImageUrl()) ? 0 : 1);
}
return score;
}
}

View File

@ -0,0 +1,228 @@
package de.danoeh.antennapod.ui.chapters;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.net.common.AntennapodHttpClient;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.parser.feed.PodcastIndexChapterParser;
import de.danoeh.antennapod.parser.media.id3.ChapterReader;
import de.danoeh.antennapod.parser.media.id3.ID3ReaderException;
import de.danoeh.antennapod.model.playback.Playable;
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentChapterReader;
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentReaderException;
import okhttp3.CacheControl;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.input.CountingInputStream;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Utility class for getting chapter data from media files.
*/
public class ChapterUtils {
private static final String TAG = "ChapterUtils";
private ChapterUtils() {
}
public static void loadChapters(Playable playable, Context context, boolean forceRefresh) {
if (playable.getChapters() != null && !forceRefresh) {
// Already loaded
return;
}
try {
List<Chapter> chaptersFromDatabase = null;
List<Chapter> chaptersFromPodcastIndex = null;
if (playable instanceof FeedMedia) {
FeedMedia feedMedia = (FeedMedia) playable;
if (feedMedia.getItem() == null) {
feedMedia.setItem(DBReader.getFeedItem(feedMedia.getItemId()));
}
if (feedMedia.getItem().hasChapters()) {
chaptersFromDatabase = DBReader.loadChaptersOfFeedItem(feedMedia.getItem());
}
if (!TextUtils.isEmpty(feedMedia.getItem().getPodcastIndexChapterUrl())) {
chaptersFromPodcastIndex = ChapterUtils.loadChaptersFromUrl(
feedMedia.getItem().getPodcastIndexChapterUrl(), forceRefresh);
}
}
List<Chapter> chaptersFromMediaFile = ChapterUtils.loadChaptersFromMediaFile(playable, context);
List<Chapter> chaptersMergePhase1 = ChapterMerger.merge(chaptersFromDatabase, chaptersFromMediaFile);
List<Chapter> chapters = ChapterMerger.merge(chaptersMergePhase1, chaptersFromPodcastIndex);
if (chapters == null) {
// Do not try loading again. There are no chapters or parsing failed.
playable.setChapters(Collections.emptyList());
} else {
playable.setChapters(chapters);
}
} catch (InterruptedIOException e) {
Log.d(TAG, "Chapter loading interrupted");
playable.setChapters(null); // Allow later retry
}
}
public static List<Chapter> loadChaptersFromMediaFile(Playable playable, Context context)
throws InterruptedIOException {
try (CountingInputStream in = openStream(playable, context)) {
List<Chapter> chapters = readId3ChaptersFrom(in);
if (!chapters.isEmpty()) {
Log.i(TAG, "Chapters loaded");
return chapters;
}
} catch (InterruptedIOException e) {
throw e;
} catch (IOException | ID3ReaderException e) {
Log.e(TAG, "Unable to load ID3 chapters: " + e.getMessage());
}
try (CountingInputStream in = openStream(playable, context)) {
List<Chapter> chapters = readOggChaptersFromInputStream(in);
if (!chapters.isEmpty()) {
Log.i(TAG, "Chapters loaded");
return chapters;
}
} catch (InterruptedIOException e) {
throw e;
} catch (IOException | VorbisCommentReaderException e) {
Log.e(TAG, "Unable to load vorbis chapters: " + e.getMessage());
}
return null;
}
private static CountingInputStream openStream(Playable playable, Context context) throws IOException {
if (playable.localFileAvailable()) {
if (playable.getLocalFileUrl() == null) {
throw new IOException("No local url");
}
File source = new File(playable.getLocalFileUrl());
if (!source.exists()) {
throw new IOException("Local file does not exist");
}
return new CountingInputStream(new BufferedInputStream(new FileInputStream(source)));
} else if (playable.getStreamUrl().startsWith(ContentResolver.SCHEME_CONTENT)) {
Uri uri = Uri.parse(playable.getStreamUrl());
return new CountingInputStream(new BufferedInputStream(context.getContentResolver().openInputStream(uri)));
} else {
Request request = new Request.Builder().url(playable.getStreamUrl()).build();
Response response = AntennapodHttpClient.getHttpClient().newCall(request).execute();
if (response.body() == null) {
throw new IOException("Body is null");
}
return new CountingInputStream(new BufferedInputStream(response.body().byteStream()));
}
}
public static List<Chapter> loadChaptersFromUrl(String url, boolean forceRefresh) throws InterruptedIOException {
if (forceRefresh) {
return loadChaptersFromUrl(url, CacheControl.FORCE_NETWORK);
}
List<Chapter> cachedChapters = loadChaptersFromUrl(url, CacheControl.FORCE_CACHE);
if (cachedChapters == null || cachedChapters.size() <= 1) {
// Some publishers use one dummy chapter before actual chapters are available
return loadChaptersFromUrl(url, CacheControl.FORCE_NETWORK);
}
return cachedChapters;
}
private static List<Chapter> loadChaptersFromUrl(String url, CacheControl cacheControl)
throws InterruptedIOException {
Response response = null;
try {
Request request = new Request.Builder().url(url).cacheControl(cacheControl).build();
response = AntennapodHttpClient.getHttpClient().newCall(request).execute();
if (response.isSuccessful() && response.body() != null) {
return PodcastIndexChapterParser.parse(response.body().string());
}
} catch (InterruptedIOException e) {
throw e;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (response != null) {
response.close();
}
}
return null;
}
@NonNull
private static List<Chapter> readId3ChaptersFrom(CountingInputStream in) throws IOException, ID3ReaderException {
ChapterReader reader = new ChapterReader(in);
reader.readInputStream();
List<Chapter> chapters = reader.getChapters();
Collections.sort(chapters, new ChapterStartTimeComparator());
enumerateEmptyChapterTitles(chapters);
if (!chaptersValid(chapters)) {
Log.e(TAG, "Chapter data was invalid");
return Collections.emptyList();
}
return chapters;
}
@NonNull
private static List<Chapter> readOggChaptersFromInputStream(InputStream input) throws VorbisCommentReaderException {
VorbisCommentChapterReader reader = new VorbisCommentChapterReader(new BufferedInputStream(input));
reader.readInputStream();
List<Chapter> chapters = reader.getChapters();
if (chapters == null) {
return Collections.emptyList();
}
Collections.sort(chapters, new ChapterStartTimeComparator());
enumerateEmptyChapterTitles(chapters);
if (chaptersValid(chapters)) {
return chapters;
}
return Collections.emptyList();
}
/**
* Makes sure that chapter does a title and an item attribute.
*/
private static void enumerateEmptyChapterTitles(List<Chapter> chapters) {
for (int i = 0; i < chapters.size(); i++) {
Chapter c = chapters.get(i);
if (c.getTitle() == null) {
c.setTitle(Integer.toString(i));
}
}
}
private static boolean chaptersValid(List<Chapter> chapters) {
if (chapters.isEmpty()) {
return false;
}
for (Chapter c : chapters) {
if (c.getStart() < 0) {
return false;
}
}
return true;
}
public static class ChapterStartTimeComparator implements Comparator<Chapter> {
@Override
public int compare(Chapter lhs, Chapter rhs) {
return Long.compare(lhs.getStart(), rhs.getStart());
}
}
}

View File

@ -16,6 +16,8 @@ dependencies {
implementation "androidx.viewpager2:viewpager2:$viewPager2Version"
implementation "com.google.android.material:material:$googleMaterialVersion"
implementation "androidx.core:core-splashscreen:1.0.0"
implementation "org.apache.commons:commons-lang3:$commonslangVersion"
implementation "commons-io:commons-io:$commonsioVersion"
testImplementation "junit:junit:$junitVersion"
}

View File

@ -0,0 +1,54 @@
package de.danoeh.antennapod.ui.common;
import android.content.Context;
import android.content.DialogInterface;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import android.util.Log;
/**
* Creates an AlertDialog which asks the user to confirm something. Other
* classes can handle events like confirmation or cancellation.
*/
public abstract class ConfirmationDialog {
private static final String TAG = ConfirmationDialog.class.getSimpleName();
private final Context context;
private final int titleId;
private final String message;
private int positiveText;
public ConfirmationDialog(Context context, int titleId, int messageId) {
this(context, titleId, context.getString(messageId));
}
public ConfirmationDialog(Context context, int titleId, String message) {
this.context = context;
this.titleId = titleId;
this.message = message;
}
private void onCancelButtonPressed(DialogInterface dialog) {
Log.d(TAG, "Dialog was cancelled");
dialog.dismiss();
}
public void setPositiveText(int id) {
this.positiveText = id;
}
public abstract void onConfirmButtonPressed(DialogInterface dialog);
public final AlertDialog createNewDialog() {
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
builder.setTitle(titleId);
builder.setMessage(message);
builder.setPositiveButton(positiveText != 0 ? positiveText : R.string.confirm_label,
(dialog, which) -> onConfirmButtonPressed(dialog));
builder.setNegativeButton(R.string.cancel_label, (dialog, which) -> onCancelButtonPressed(dialog));
builder.setOnCancelListener(ConfirmationDialog.this::onCancelButtonPressed);
return builder.create();
}
}

View File

@ -0,0 +1,67 @@
package de.danoeh.antennapod.ui.common;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Locale;
public class IntentUtils {
private static final String TAG = "IntentUtils";
private IntentUtils(){}
/*
* Checks if there is at least one exported activity that can be performed for the intent
*/
public static boolean isCallable(final Context context, final Intent intent) {
List<ResolveInfo> list = context.getPackageManager().queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
for(ResolveInfo info : list) {
if(info.activityInfo.exported) {
return true;
}
}
return false;
}
public static void sendLocalBroadcast(Context context, String action) {
context.sendBroadcast(new Intent(action).setPackage(context.getPackageName()));
}
public static void openInBrowser(Context context, String url) {
try {
Intent myIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
myIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(myIntent);
} catch (ActivityNotFoundException e) {
Toast.makeText(context, R.string.pref_no_browser_found, Toast.LENGTH_LONG).show();
Log.e(TAG, Log.getStackTraceString(e));
}
}
public static String getLocalizedWebsiteLink(Context context) {
try (InputStream is = context.getAssets().open("website-languages.txt")) {
String[] languages = IOUtils.toString(is, StandardCharsets.UTF_8.name()).split("\n");
String deviceLanguage = Locale.getDefault().getLanguage();
if (ArrayUtils.contains(languages, deviceLanguage) && !"en".equals(deviceLanguage)) {
return "https://antennapod.org/" + deviceLanguage;
} else {
return "https://antennapod.org";
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<gradient
android:angle="90"
android:endColor="@color/gradient_025"
android:startColor="@color/gradient_075"
android:type="linear" />
<corners
android:radius="0dp"/>
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="?attr/colorPrimary" />
<corners android:radius="30dp" />
<size android:width="60dp" android:height="60dp"/>
</shape>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?attr/colorSurfaceVariant">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="@color/black"/>
<corners android:radius="32dp"/>
</shape>
</item>
<item>
<selector>
<item android:state_selected="true">
<shape android:shape="rectangle">
<solid android:color="?attr/colorSurfaceVariant"/>
<corners android:radius="32dp"/>
</shape>
</item>
<item android:drawable="@android:color/transparent" />
</selector>
</item>
</ripple>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<gradient
android:angle="90"
android:endColor="#00ffffff"
android:startColor="#ffffffff"
android:type="linear" />
<corners
android:radius="0dp"/>
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="1dp"
android:color="?attr/colorPrimary" />
<corners android:radius="20dp" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
</shape>

View File

@ -23,7 +23,6 @@ android {
}
dependencies {
implementation project(":core")
implementation project(":event")
implementation project(":net:common")
implementation project(":net:sync:model")

View File

@ -10,7 +10,7 @@ import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceFragmentCompat;
import com.google.android.material.snackbar.Snackbar;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.ui.common.IntentUtils;
import de.danoeh.antennapod.ui.preferences.BuildConfig;
import de.danoeh.antennapod.ui.preferences.R;

View File

@ -9,7 +9,7 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import androidx.fragment.app.ListFragment;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.ui.common.IntentUtils;
import de.danoeh.antennapod.ui.preferences.R;
import io.reactivex.Single;
import io.reactivex.SingleOnSubscribe;

View File

@ -1,6 +1,7 @@
package de.danoeh.antennapod.ui.preferences.screen.downloads;
import android.content.Context;
import android.os.StatFs;
import android.text.format.Formatter;
import android.view.LayoutInflater;
import android.view.View;
@ -12,7 +13,6 @@ import androidx.annotation.NonNull;
import androidx.core.util.Consumer;
import androidx.recyclerview.widget.RecyclerView;
import de.danoeh.antennapod.storage.preferences.UserPreferences;
import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.ui.preferences.R;
import java.io.File;
@ -125,11 +125,17 @@ public class DataFolderAdapter extends RecyclerView.Adapter<DataFolderAdapter.Vi
}
long getAvailableSpace() {
return StorageUtils.getFreeSpaceAvailable(path);
StatFs stat = new StatFs(path);
long availableBlocks = stat.getAvailableBlocksLong();
long blockSize = stat.getBlockSizeLong();
return availableBlocks * blockSize;
}
long getTotalSpace() {
return StorageUtils.getTotalSpaceAvailable(path);
StatFs stat = new StatFs(path);
long blockCount = stat.getBlockCountLong();
long blockSize = stat.getBlockSizeLong();
return blockCount * blockSize;
}
int getUsagePercentage() {

View File

@ -0,0 +1,277 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="spnAutoDeleteItems">
<item>@string/global_default</item>
<item>@string/feed_auto_download_always</item>
<item>@string/feed_auto_download_never</item>
</string-array>
<string-array name="spnAutoDeleteValues">
<item>global</item>
<item>always</item>
<item>never</item>
</string-array>
<string-array name="spnVolumeAdaptationItems">
<item>@string/feed_volume_reduction_heavy</item>
<item>@string/feed_volume_reduction_light</item>
<item>@string/feed_volume_reduction_off</item>
<item>@string/feed_volume_boost_light</item>
<item>@string/feed_volume_boost_medium</item>
<item>@string/feed_volume_boost_heavy</item>
</string-array>
<string-array name="spnVolumeAdaptationValues">
<item>heavy</item>
<item>light</item>
<item>off</item>
<item>light_boost</item>
<item>medium_boost</item>
<item>heavy_boost</item>
</string-array>
<string-array name="feed_refresh_interval_entries">
<item>@string/feed_refresh_never</item>
<item>@string/feed_every_hour</item>
<item>@string/feed_every_2_hours</item>
<item>@string/feed_every_4_hours</item>
<item>@string/feed_every_8_hours</item>
<item>@string/feed_every_12_hours</item>
<item>@string/feed_every_24_hours</item>
<item>@string/feed_every_72_hours</item>
</string-array>
<string-array name="feed_refresh_interval_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>4</item>
<item>8</item>
<item>12</item>
<item>24</item>
<item>72</item>
</string-array>
<string-array name="globalNewEpisodesActionItems">
<item>@string/feed_new_episodes_action_add_to_inbox</item>
<item>@string/feed_new_episodes_action_add_to_queue</item>
<item>@string/feed_new_episodes_action_nothing</item>
</string-array>
<string-array name="globalNewEpisodesActionValues">
<item>1</item>
<item>3</item>
<item>2</item>
</string-array>
<string-array name="feedNewEpisodesActionItems">
<item>@string/global_default</item>
<item>@string/feed_new_episodes_action_add_to_inbox</item>
<item>@string/feed_new_episodes_action_add_to_queue</item>
<item>@string/feed_new_episodes_action_nothing</item>
</string-array>
<string-array name="feedNewEpisodesActionValues">
<item>0</item>
<item>1</item>
<item>3</item>
<item>2</item>
</string-array>
<string-array name="smart_mark_as_played_values">
<item>0</item>
<item>15</item>
<item>30</item>
<item>60</item>
<item>120</item>
<item>300</item>
</string-array>
<integer-array name="seek_delta_values">
<item>5</item>
<item>10</item>
<item>15</item>
<item>20</item>
<item>30</item>
<item>45</item>
<item>60</item>
</integer-array>
<string-array name="episode_cache_size_entries">
<item>5</item>
<item>10</item>
<item>25</item>
<item>50</item>
<item>100</item>
<item>500</item>
<item>@string/pref_episode_cache_unlimited</item>
</string-array>
<string-array name="episode_cache_size_values">
<item>5</item>
<item>10</item>
<item>25</item>
<item>50</item>
<item>100</item>
<item>500</item>
<item>-1</item>
</string-array>
<string-array name="mobile_update_entries">
<item>@string/pref_mobileUpdate_refresh</item>
<item>@string/pref_mobileUpdate_episode_download</item>
<item>@string/pref_mobileUpdate_auto_download</item>
<item>@string/pref_mobileUpdate_streaming</item>
<item>@string/pref_mobileUpdate_images</item>
<item>@string/synchronization_pref</item>
</string-array>
<string-array name="mobile_update_values">
<item>feed_refresh</item>
<item>episode_download</item>
<item>auto_download</item>
<item>streaming</item>
<item>images</item>
<item>sync</item>
</string-array>
<string-array name="mobile_update_default_value">
<item>images</item>
<item>sync</item>
</string-array>
<string-array name="episode_cleanup_entries">
<item>@string/episode_cleanup_except_favorite_removal</item>
<item>@string/episode_cleanup_queue_removal</item>
<item>0</item>
<item>1</item>
<item>3</item>
<item>5</item>
<item>7</item>
<item>@string/episode_cleanup_never</item>
</string-array>
<string-array name="button_action_options">
<item>@string/button_action_fast_forward</item>
<item>@string/button_action_rewind</item>
<item>@string/button_action_skip_episode</item>
<item>@string/button_action_restart_episode</item>
</string-array>
<string-array name="button_action_values">
<item>@string/keycode_media_fast_forward</item>
<item>@string/keycode_media_rewind</item>
<item>@string/keycode_media_next</item>
<item>@string/keycode_media_previous</item>
</string-array>
<string-array name="enqueue_location_options">
<item>@string/enqueue_location_back</item>
<item>@string/enqueue_location_front</item>
<item>@string/enqueue_location_after_current</item>
<item>@string/enqueue_location_random</item>
</string-array>
<string-array name="enqueue_location_values">
<!-- MUST be the same as UserPreferences.EnqueueLocation enum -->
<item>BACK</item>
<item>FRONT</item>
<item>AFTER_CURRENTLY_PLAYING</item>
<item>RANDOM</item>
</string-array>
<string-array name="episode_cleanup_values">
<item>-3</item>
<item>-1</item>
<item>0</item>
<item>12</item>
<item>24</item>
<item>72</item>
<item>120</item>
<item>168</item>
<item>-2</item>
</string-array>
<string-array name="nav_drawer_titles">
<item>@string/home_label</item>
<item>@string/queue_label</item>
<item>@string/inbox_label</item>
<item>@string/episodes_label</item>
<item>@string/subscriptions_label</item>
<item>@string/downloads_label</item>
<item>@string/playback_history_label</item>
<item>@string/add_feed_label</item>
<item>@string/subscriptions_list_label</item>
</string-array>
<string-array name="nav_drawer_feed_order_options">
<item>@string/drawer_feed_order_unplayed_episodes</item>
<item>@string/drawer_feed_order_alphabetical</item>
<item>@string/drawer_feed_order_last_update</item>
<item>@string/drawer_feed_order_most_played</item>
</string-array>
<string-array name="nav_drawer_feed_order_values">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<string-array name="nav_drawer_feed_counter_options">
<item>@string/drawer_feed_counter_inbox</item>
<item>@string/drawer_feed_counter_unplayed</item>
<item>@string/drawer_feed_counter_downloaded</item>
<item>@string/drawer_feed_counter_downloaded_unplayed</item>
<item>@string/drawer_feed_counter_none</item>
</string-array>
<string-array name="nav_drawer_feed_counter_values">
<item>1</item>
<item>2</item>
<item>4</item>
<item>5</item>
<item>3</item>
</string-array>
<string-array name="home_section_titles">
<item>@string/home_continue_title</item>
<item>@string/home_new_title</item>
<item>@string/home_surprise_title</item>
<item>@string/home_classics_title</item>
<item>@string/home_downloads_title</item>
</string-array>
<string-array name="home_section_tags">
<item>QueueSection</item>
<item>InboxSection</item>
<item>EpisodesSurpriseSection</item>
<item>SubscriptionsSection</item>
<item>DownloadsSection</item>
</string-array>
<string-array name="full_notification_buttons_options">
<item>@string/skip_episode_label</item>
<item>@string/next_chapter</item>
<item>@string/playback_speed</item>
<item>@string/sleep_timer_label</item>
</string-array>
<string-array name="default_page_values">
<item>HomeFragment</item>
<item>QueueFragment</item>
<item>NewEpisodesFragment</item>
<item>EpisodesFragment</item>
<item>SubscriptionFragment</item>
<item>remember</item>
</string-array>
<string-array name="default_page_titles">
<item>@string/home_label</item>
<item>@string/queue_label</item>
<item>@string/inbox_label</item>
<item>@string/episodes_label</item>
<item>@string/subscriptions_label</item>
<item>@string/remember_last_page</item>
</string-array>
</resources>

View File

@ -0,0 +1,9 @@
<resources
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingTranslation">
<string name="keycode_media_next">87</string>
<string name="keycode_media_previous">88</string>
<string name="keycode_media_rewind">89</string>
<string name="keycode_media_fast_forward">90</string>
</resources>

View File

@ -9,7 +9,6 @@ android {
}
dependencies {
implementation project(":core")
implementation project(":event")
implementation project(":model")
implementation project(':storage:database')

View File

@ -18,7 +18,7 @@ import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
import de.danoeh.antennapod.core.util.ConfirmationDialog;
import de.danoeh.antennapod.ui.common.ConfirmationDialog;
import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.event.StatisticsEvent;
import de.danoeh.antennapod.ui.common.PagedToolbarFragment;

View File

@ -13,7 +13,7 @@ import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.StatisticsItem;
import de.danoeh.antennapod.ui.common.Converter;
import de.danoeh.antennapod.ui.common.DateFormatter;
import de.danoeh.antennapod.core.util.ReleaseScheduleGuesser;
import de.danoeh.antennapod.storage.database.ReleaseScheduleGuesser;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedItemFilter;
import de.danoeh.antennapod.model.feed.SortOrder;