Open podcast or episode from download log details (#7867)

This commit is contained in:
schasi
2025-11-30 23:02:47 +01:00
committed by GitHub
parent cafb52766b
commit f47134a7eb
9 changed files with 457 additions and 68 deletions

View File

@ -0,0 +1,126 @@
package de.test.antennapod.ui;
import android.content.Context;
import android.content.Intent;
import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.model.download.DownloadError;
import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedItem;
import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.storage.database.FeedDatabaseWriter;
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
import de.danoeh.antennapod.ui.screen.download.CompletedDownloadsFragment;
import de.test.antennapod.EspressoTestUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Collections;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static de.test.antennapod.EspressoTestUtils.waitForView;
import static de.test.antennapod.EspressoTestUtils.waitForViewGlobally;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.Matchers.allOf;
@RunWith(AndroidJUnit4.class)
public class DownloadLogTest {
@Rule
public IntentsTestRule<MainActivity> activityRule = new IntentsTestRule<>(MainActivity.class, false, false);
private Context context;
private Intent completedDownloadsIntent;
private Feed feed;
private FeedMedia media;
@Before
public void setUp() throws Exception {
EspressoTestUtils.clearPreferences();
EspressoTestUtils.clearDatabase();
context = InstrumentationRegistry.getInstrumentation().getTargetContext();
completedDownloadsIntent = new Intent(context, MainActivity.class);
completedDownloadsIntent.putExtra(MainActivityStarter.EXTRA_FRAGMENT_TAG, CompletedDownloadsFragment.TAG);
feed = new Feed(0, "last modified", "@@Feed title@@", "link", "description", "payment link",
"@author@", "language", "type", "feedIdentifier", "http://localhost/cover.png",
"/sdcard/abc", "http://localhost/feed.xml", 0);
FeedItem item = new FeedItem(0, "title", "identifier", "link", new Date(), FeedItem.UNPLAYED, feed);
media = new FeedMedia(item, "http://localhost/media.mp3", 10000, "mime type");
item.setMedia(media);
feed.setItems(Collections.singletonList(item));
feed = FeedDatabaseWriter.updateFeed(context, feed, false);
}
@Test
public void testExistingSubscribedFeed() {
DownloadResult result = new DownloadResult("@@Title@@", feed.getId(),
Feed.FEEDFILETYPE_FEED, false, DownloadError.ERROR_IO_ERROR, "@@reason@@");
openDialog(result);
// Open feed
onView(withText(R.string.download_log_open_feed)).perform(click());
waitForViewGlobally(withText(feed.getAuthor()), 2000);
}
@Test
public void testExistingNonSubscribedFeed() throws InterruptedException, ExecutionException {
DBWriter.setFeedState(context, feed, Feed.STATE_NOT_SUBSCRIBED).get();
DownloadResult result = new DownloadResult("@@Title@@", feed.getId(),
Feed.FEEDFILETYPE_FEED, false, DownloadError.ERROR_IO_ERROR, "@@reason@@");
openDialog(result);
// Opens online feed view
onView(withText(R.string.download_log_open_feed)).perform(click());
waitForViewGlobally(withText(feed.getAuthor()), 2000);
onView(isRoot()).perform(waitForView(allOf(withText(R.string.subscribe_label), isDisplayed()), 2000));
}
@Test
public void testNonExistingFeed() {
DownloadResult result = new DownloadResult("@@Title@@", feed.getId() + 1,
Feed.FEEDFILETYPE_FEED, false, DownloadError.ERROR_IO_ERROR, "@@reason@@");
openDialog(result);
// Does not have button
onView(withText(R.string.download_log_open_feed)).check(matches(not(isDisplayed())));
}
@Test
public void testExistingMedia() {
DownloadResult result = new DownloadResult("@@Title@@", media.getId(),
FeedMedia.FEEDFILETYPE_FEEDMEDIA, false, DownloadError.ERROR_IO_ERROR, "@@reason@@");
openDialog(result);
// Opens feed
onView(withText(R.string.download_log_open_feed)).perform(click());
waitForViewGlobally(withText(feed.getAuthor()), 2000);
}
@Test
public void testNonExistingMedia() {
DownloadResult result = new DownloadResult("@@Title@@", media.getId() + 1,
FeedMedia.FEEDFILETYPE_FEEDMEDIA, false, DownloadError.ERROR_IO_ERROR, "@@reason@@");
openDialog(result);
// Does not have button
onView(withText(R.string.download_log_open_feed)).check(matches(not(isDisplayed())));
}
void openDialog(DownloadResult result) {
DBWriter.addDownloadStatus(result);
activityRule.launchActivity(completedDownloadsIntent);
onView(withContentDescription(R.string.downloads_log_label)).perform(click());
onView(isRoot()).perform(waitForView(allOf(withText(result.getTitle()), isDisplayed()), 1000));
onView(withText(result.getTitle())).perform(click());
onView(isRoot()).perform(waitForView(allOf(withText(result.getReasonDetailed()), isDisplayed()), 1000));
}
}

View File

@ -707,7 +707,7 @@ public class MainActivity extends CastEnabledActivity {
drawerLayout.open();
}
if (intent.getBooleanExtra(MainActivityStarter.EXTRA_OPEN_DOWNLOAD_LOGS, false)) {
new DownloadLogFragment().show(getSupportFragmentManager(), null);
new DownloadLogFragment().show(getSupportFragmentManager(), DownloadLogFragment.TAG);
}
if (intent.getBooleanExtra(EXTRA_REFRESH_ON_START, false)) {
FeedUpdateManager.getInstance().runOnceOrAsk(this);

View File

@ -131,7 +131,7 @@ public class CompletedDownloadsFragment extends Fragment
return true;
});
if (getArguments() != null && getArguments().getBoolean(ARG_SHOW_LOGS, false)) {
new DownloadLogFragment().show(getChildFragmentManager(), null);
new DownloadLogFragment().show(getChildFragmentManager(), DownloadLogFragment.TAG);
}
addEmptyView();
@ -176,7 +176,7 @@ public class CompletedDownloadsFragment extends Fragment
FeedUpdateManager.getInstance().runOnceOrAsk(requireContext());
return true;
} else if (item.getItemId() == R.id.action_download_logs) {
new DownloadLogFragment().show(getChildFragmentManager(), null);
new DownloadLogFragment().show(getChildFragmentManager(), DownloadLogFragment.TAG);
return true;
} else if (item.getItemId() == R.id.action_search) {
((MainActivity) getActivity()).loadChildFragment(SearchFragment.newInstance());

View File

@ -1,71 +1,169 @@
package de.danoeh.antennapod.ui.screen.download;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Build;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.widget.TextView;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.databinding.DownloadLogDetailsDialogBinding;
import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.event.MessageEvent;
import de.danoeh.antennapod.model.feed.Feed;
import de.danoeh.antennapod.model.feed.FeedMedia;
import org.greenrobot.eventbus.EventBus;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
import de.danoeh.antennapod.ui.appstartintent.OnlineFeedviewActivityStarter;
import de.danoeh.antennapod.ui.common.ClipboardUtils;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class DownloadLogDetailsDialog extends MaterialAlertDialogBuilder {
/**
* Shows a dialog with Feed title (and FeedItem title if possible).
* Can show a button to jump to the feed details view.
*/
public class DownloadLogDetailsDialog extends DialogFragment {
public static final String TAG = "DownloadLogDetails";
private static final String EXTRA_IS_JUMP_TO_FEED = "isJumpToFeed";
private static final String EXTRA_DOWNLOAD_RESULT = "downloadResult";
private DownloadLogDetailsDialogBinding viewBinding;
private Disposable disposable;
private boolean isJumpToFeed;
private DownloadResult downloadResult;
private Feed feed = null;
private String podcastName = null;
private String episodeName = null;
private String url = "unknown";
private String clipboardContent = "";
public DownloadLogDetailsDialog(@NonNull Context context, DownloadResult status) {
super(context);
String url = "unknown";
if (status.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
FeedMedia media = DBReader.getFeedMedia(status.getFeedfileId());
if (media != null) {
url = media.getDownloadUrl();
}
} else if (status.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
Feed feed = DBReader.getFeed(status.getFeedfileId(), false, 0, 0);
if (feed != null) {
url = feed.getDownloadUrl();
}
}
String message = context.getString(R.string.download_successful);
if (!status.isSuccessful()) {
message = status.getReasonDetailed();
}
String humanReadableReason = context.getString(DownloadErrorLabel.from(status.getReason()));
SpannableString errorMessage = new SpannableString(context.getString(R.string.download_log_details_message,
humanReadableReason, message, url));
errorMessage.setSpan(new ForegroundColorSpan(0x88888888),
humanReadableReason.length(), errorMessage.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
setTitle(R.string.download_error_details);
setMessage(errorMessage);
setPositiveButton(android.R.string.ok, null);
setNeutralButton(R.string.copy_to_clipboard, (dialog, which) -> {
ClipboardManager clipboard = (ClipboardManager) getContext()
.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(context.getString(R.string.download_error_details), errorMessage);
clipboard.setPrimaryClip(clip);
if (Build.VERSION.SDK_INT < 32) {
EventBus.getDefault().post(new MessageEvent(context.getString(R.string.copied_to_clipboard)));
}
});
public static DownloadLogDetailsDialog newInstance(DownloadResult downloadResult, boolean isJumpToFeed) {
DownloadLogDetailsDialog dialog = new DownloadLogDetailsDialog();
Bundle args = new Bundle();
args.putSerializable(EXTRA_DOWNLOAD_RESULT, downloadResult);
args.putBoolean(EXTRA_IS_JUMP_TO_FEED, isJumpToFeed);
dialog.setArguments(args);
return dialog;
}
@Override
public AlertDialog show() {
AlertDialog dialog = super.show();
((TextView) dialog.findViewById(android.R.id.message)).setTextIsSelectable(true);
return dialog;
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
downloadResult = (DownloadResult) getArguments().getSerializable(EXTRA_DOWNLOAD_RESULT);
isJumpToFeed = getArguments().getBoolean(EXTRA_IS_JUMP_TO_FEED, true);
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
MaterialAlertDialogBuilder dialog = new MaterialAlertDialogBuilder(getContext());
dialog.setTitle(R.string.download_error_details);
dialog.setPositiveButton(android.R.string.ok, null);
dialog.setNeutralButton(R.string.copy_to_clipboard, (copyDialog, which) ->
ClipboardUtils.copyText(viewBinding.getRoot(), R.string.download_error_details, clipboardContent));
viewBinding = DownloadLogDetailsDialogBinding.inflate(getLayoutInflater());
dialog.setView(viewBinding.getRoot());
viewBinding.goToPodcastButton.setVisibility(View.GONE);
viewBinding.goToPodcastButton.setOnClickListener(v -> {
goToFeed();
dismiss();
Fragment downloadLog = getParentFragmentManager().findFragmentByTag(DownloadLogFragment.TAG);
if (downloadLog instanceof DownloadLogFragment) {
((DownloadLogFragment) downloadLog).dismiss();
}
});
viewBinding.fileUrlLabel.setOnClickListener(v ->
ClipboardUtils.copyText(viewBinding.fileUrlLabel, R.string.download_log_details_file_url_title));
viewBinding.technicalReasonLabel.setOnClickListener(v ->
ClipboardUtils.copyText(viewBinding.technicalReasonLabel,
R.string.download_log_details_technical_reason_title));
loadData();
return dialog.create();
}
@Override
public void onDestroy() {
super.onDestroy();
if (disposable != null) {
disposable.dispose();
}
}
private void loadData() {
if (disposable != null) {
disposable.dispose();
}
disposable = Single.create(emitter -> {
if (downloadResult.getFeedfileType() == FeedMedia.FEEDFILETYPE_FEEDMEDIA) {
FeedMedia media = DBReader.getFeedMedia(downloadResult.getFeedfileId());
if (media != null) {
if (media.getItem() != null && media.getItem().getFeed() != null) {
feed = media.getItem().getFeed();
podcastName = feed.getTitle();
}
episodeName = media.getEpisodeTitle();
url = media.getDownloadUrl();
} else {
episodeName = downloadResult.getTitle();
}
} else if (downloadResult.getFeedfileType() == Feed.FEEDFILETYPE_FEED) {
feed = DBReader.getFeed(downloadResult.getFeedfileId(), false, 0, 0);
if (feed != null) {
podcastName = feed.getTitle();
url = feed.getDownloadUrl();
} else {
podcastName = downloadResult.getTitle();
}
}
emitter.onSuccess(true);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(obj -> updateUi(),
error -> Log.e(TAG, Log.getStackTraceString(error)));
}
private void updateUi() {
String message = getString(R.string.download_successful);
if (!downloadResult.isSuccessful()) {
message = downloadResult.getReasonDetailed();
}
viewBinding.goToPodcastButton.setVisibility((isJumpToFeed && feed != null) ? View.VISIBLE : View.GONE);
viewBinding.podcastNameLabel.setText(podcastName);
viewBinding.podcastContainer.setVisibility(podcastName == null ? View.GONE : View.VISIBLE);
viewBinding.episodeNameLabel.setText(episodeName);
viewBinding.episodeContainer.setVisibility(episodeName == null ? View.GONE : View.VISIBLE);
final String humanReadableReason = getString(DownloadErrorLabel.from(downloadResult.getReason()));
viewBinding.humanReadableReasonLabel.setText(humanReadableReason);
viewBinding.technicalReasonLabel.setText(message);
viewBinding.fileUrlLabel.setText(url);
final String humanReadableReasonTitle = getString(R.string.download_log_details_human_readable_reason_title);
final String technicalReasonTitle = getString(R.string.download_log_details_technical_reason_title);
final String urlTitle = getString(R.string.download_log_details_file_url_title);
clipboardContent = String.format("%s: \n%s \n\n%s: \n%s \n\n%s: \n%s",
humanReadableReasonTitle, humanReadableReason, technicalReasonTitle, message, urlTitle, url);
}
void goToFeed() {
if (feed == null) {
return;
}
Intent intent;
if (feed.getState() == Feed.STATE_SUBSCRIBED) {
intent = new MainActivityStarter(getContext()).withOpenFeed(feed.getId()).getIntent();
} else {
intent = new OnlineFeedviewActivityStarter(getContext(), feed.getDownloadUrl()).getIntent();
}
getContext().startActivity(intent);
}
}

View File

@ -12,14 +12,14 @@ import androidx.annotation.Nullable;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.databinding.DownloadLogFragmentBinding;
import de.danoeh.antennapod.event.DownloadLogEvent;
import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.storage.database.DBReader;
import de.danoeh.antennapod.storage.database.DBWriter;
import de.danoeh.antennapod.databinding.DownloadLogFragmentBinding;
import de.danoeh.antennapod.model.download.DownloadResult;
import de.danoeh.antennapod.ui.view.EmptyViewHandler;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import org.greenrobot.eventbus.EventBus;
@ -33,7 +33,7 @@ import java.util.List;
*/
public class DownloadLogFragment extends BottomSheetDialogFragment
implements AdapterView.OnItemClickListener, MaterialToolbar.OnMenuItemClickListener {
private static final String TAG = "DownloadLogFragment";
public static final String TAG = "DownloadLogFragment";
private List<DownloadResult> downloadLog = new ArrayList<>();
private DownloadLogAdapter adapter;
@ -86,7 +86,8 @@ public class DownloadLogFragment extends BottomSheetDialogFragment
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final DownloadResult item = adapter.getItem(position);
if (item != null) {
new DownloadLogDetailsDialog(getContext(), item).show();
DownloadLogDetailsDialog.newInstance(item, true)
.show(getParentFragmentManager(), DownloadLogDetailsDialog.TAG);
}
}

View File

@ -624,9 +624,10 @@ public class FeedItemlistFragment extends Fragment implements AdapterView.OnItem
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
downloadStatus -> new DownloadLogDetailsDialog(getContext(), downloadStatus).show(),
downloadStatus -> DownloadLogDetailsDialog.newInstance(downloadStatus, false)
.show(getChildFragmentManager(), DownloadLogDetailsDialog.TAG),
error -> error.printStackTrace(),
() -> new DownloadLogFragment().show(getChildFragmentManager(), null));
() -> new DownloadLogFragment().show(getChildFragmentManager(), DownloadLogFragment.TAG));
}
private void showFeedInfo() {

View File

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="?dialogPreferredPadding">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Podcast header, podcast title, button to jump to podcast -->
<LinearLayout
android:id="@+id/podcastContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/feed"
android:layout_marginEnd="4dp"
style="@style/TextAppearance.Material3.TitleMedium" />
<TextView
android:id="@+id/podcastNameLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="end"
tools:text="Podcast name" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/goToPodcastButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="0dp"
android:text="@string/download_log_open_feed"
android:layout_marginStart="8dp"
style="@style/Widget.Material3.Button.TextButton" />
</LinearLayout>
<!-- Episode header, episode title -->
<LinearLayout
android:id="@+id/episodeContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/episode"
android:layout_marginEnd="4dp"
style="@style/TextAppearance.Material3.TitleMedium" />
<TextView
android:id="@+id/episodeNameLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="middle"
tools:text="Episode Name" />
</LinearLayout>
<!-- Human readable reason for the message/error -->
<LinearLayout
android:id="@+id/humanReadableReasonContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download_log_details_human_readable_reason_title"
android:layout_marginEnd="4dp"
style="@style/TextAppearance.Material3.TitleMedium" />
<TextView
android:id="@+id/humanReadableReasonLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/download_error_not_found" />
</LinearLayout>
<!-- Technical details/reason -->
<LinearLayout
android:id="@+id/technicalReasonContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download_log_details_technical_reason_title"
android:layout_marginEnd="4dp"
style="@style/TextAppearance.Material3.TitleMedium" />
<TextView
android:id="@+id/technicalReasonLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
tools:text="Http error 404" />
</LinearLayout>
<!-- File URL -->
<LinearLayout
android:id="@+id/fileUrlContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/download_log_details_file_url_title"
android:layout_marginEnd="4dp"
style="@style/TextAppearance.Material3.TitleMedium" />
<TextView
android:id="@+id/fileUrlLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
tools:text="http://example.com/feed.xml" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -2,12 +2,15 @@ package de.danoeh.antennapod.model.download;
import androidx.annotation.NonNull;
import java.io.Serializable;
import java.util.Date;
/**
* Contains status attributes for one download
*/
public class DownloadResult {
public class DownloadResult implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Downloaders should use this constant for the size attribute if necessary
* so that the listadapters etc. can react properly.

View File

@ -304,6 +304,10 @@
<string name="download_running">Download running</string>
<string name="download_error_details">Details</string>
<string name="download_log_details_message">%1$s \n\nTechnical reason: \n%2$s \n\nFile URL:\n%3$s</string>
<string name="download_log_details_human_readable_reason_title">Status</string>
<string name="download_log_details_technical_reason_title">Technical details</string>
<string name="download_log_details_file_url_title">File URL</string>
<string name="download_log_open_feed">Open</string>
<string name="download_error_retrying">Download of \"%1$s\" failed. Will be retried later.</string>
<string name="download_error_not_retrying">Download of \"%1$s\" failed.</string>
<string name="download_error_tap_for_details">Tap to view details.</string>
@ -387,7 +391,9 @@
<string name="keep_sorted">Keep sorted</string>
<string name="date">Date</string>
<string name="duration">Duration</string>
<string name="episode">Episode</string>
<string name="episode_title">Episode title</string>
<string name="feed">Podcast</string>
<string name="feed_title">Podcast title</string>
<string name="random">Random</string>
<string name="smart_shuffle">Smart shuffle</string>