New media player screen (#5075)
Co-authored-by: jonasburian <jonas.burian@protonmail.com> Co-authored-by: ByteHamster <info@bytehamster.com>
This commit is contained in:
parent
fb6bd0cbaa
commit
292c9bf151
@ -20,6 +20,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBarDrawerToggle;
|
||||
@ -32,16 +33,23 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentTransaction;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.event.MessageEvent;
|
||||
import de.danoeh.antennapod.core.preferences.UserPreferences;
|
||||
import de.danoeh.antennapod.core.receiver.MediaButtonReceiver;
|
||||
import de.danoeh.antennapod.core.service.playback.PlaybackService;
|
||||
import de.danoeh.antennapod.core.util.StorageUtils;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.core.util.download.AutoUpdateManager;
|
||||
import de.danoeh.antennapod.dialog.RatingDialog;
|
||||
import de.danoeh.antennapod.fragment.AddFeedFragment;
|
||||
@ -57,12 +65,8 @@ import de.danoeh.antennapod.fragment.SubscriptionFragment;
|
||||
import de.danoeh.antennapod.fragment.TransitionEffect;
|
||||
import de.danoeh.antennapod.preferences.PreferenceUpgrader;
|
||||
import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter;
|
||||
import de.danoeh.antennapod.ui.common.ThemeUtils;
|
||||
import de.danoeh.antennapod.view.LockableBottomSheetBehavior;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
/**
|
||||
* The activity that is shown when the user launches the app.
|
||||
@ -184,6 +188,11 @@ public class MainActivity extends CastEnabledActivity {
|
||||
if (audioPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (slideOffset == 0.0f) { //STATE_COLLAPSED
|
||||
audioPlayer.scrollToPage(AudioPlayerFragment.POS_COVER);
|
||||
}
|
||||
|
||||
float condensedSlideOffset = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f;
|
||||
audioPlayer.getExternalPlayerHolder().setAlpha(1 - condensedSlideOffset);
|
||||
audioPlayer.getExternalPlayerHolder().setVisibility(
|
||||
|
@ -11,6 +11,7 @@ import android.widget.ImageButton;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@ -20,10 +21,18 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.List;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.activity.CastEnabledActivity;
|
||||
import de.danoeh.antennapod.activity.MainActivity;
|
||||
@ -48,20 +57,13 @@ import de.danoeh.antennapod.dialog.SkipPreferenceDialog;
|
||||
import de.danoeh.antennapod.dialog.SleepTimerDialog;
|
||||
import de.danoeh.antennapod.dialog.VariableSpeedDialog;
|
||||
import de.danoeh.antennapod.menuhandler.FeedItemMenuHandler;
|
||||
import de.danoeh.antennapod.view.ChapterSeekBar;
|
||||
import de.danoeh.antennapod.ui.common.PlaybackSpeedIndicatorView;
|
||||
import de.danoeh.antennapod.view.ChapterSeekBar;
|
||||
import de.danoeh.antennapod.view.PlayButton;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.NumberFormat;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Shows the audio player.
|
||||
@ -69,10 +71,9 @@ import java.util.List;
|
||||
public class AudioPlayerFragment extends Fragment implements
|
||||
ChapterSeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
|
||||
public static final String TAG = "AudioPlayerFragment";
|
||||
private static final int POS_COVER = 0;
|
||||
private static final int POS_SHOWNOTES = 1;
|
||||
private static final int POS_CHAPTERS = 2;
|
||||
private static final int NUM_CONTENT_FRAGMENTS = 3;
|
||||
public static final int POS_COVER = 0;
|
||||
public static final int POS_DESCRIPTION = 1;
|
||||
private static final int NUM_CONTENT_FRAGMENTS = 2;
|
||||
private static final float EPSILON = 0.001f;
|
||||
|
||||
PlaybackSpeedIndicatorView butPlaybackSpeed;
|
||||
@ -95,11 +96,9 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
private PlaybackController controller;
|
||||
private Disposable disposable;
|
||||
private boolean showTimeLeft;
|
||||
private boolean hasChapters = false;
|
||||
private boolean seekedToChapterStart = false;
|
||||
private int currentChapterIndex = -1;
|
||||
private int duration;
|
||||
private TabLayoutMediator tabLayoutMediator;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
@ -155,36 +154,9 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
}
|
||||
});
|
||||
|
||||
TabLayout tabLayout = root.findViewById(R.id.sliding_tabs);
|
||||
tabLayoutMediator = new TabLayoutMediator(tabLayout, pager, (tab, position) -> {
|
||||
tab.view.setAlpha(1.0f);
|
||||
switch (position) {
|
||||
case POS_COVER:
|
||||
tab.setText(R.string.cover_label);
|
||||
break;
|
||||
case POS_SHOWNOTES:
|
||||
tab.setText(R.string.shownotes_label);
|
||||
break;
|
||||
case POS_CHAPTERS:
|
||||
tab.setText(R.string.chapters_label);
|
||||
if (!hasChapters) {
|
||||
tab.view.setAlpha(0.5f);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
tabLayoutMediator.attach();
|
||||
return root;
|
||||
}
|
||||
|
||||
private void setHasChapters(boolean hasChapters) {
|
||||
this.hasChapters = hasChapters;
|
||||
tabLayoutMediator.detach();
|
||||
tabLayoutMediator.attach();
|
||||
}
|
||||
|
||||
private void setChapterDividers(Playable media) {
|
||||
|
||||
if (media == null) {
|
||||
@ -193,7 +165,7 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
|
||||
float[] dividerPos = null;
|
||||
|
||||
if (hasChapters) {
|
||||
if (media.getChapters() != null) {
|
||||
List<Chapter> chapters = media.getChapters();
|
||||
dividerPos = new float[chapters.size()];
|
||||
|
||||
@ -201,7 +173,7 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
dividerPos[i] = chapters.get(i).getStart() / (float) duration;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sbPosition.setDividerPos(dividerPos);
|
||||
}
|
||||
|
||||
@ -417,16 +389,7 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
if (controller == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
duration = controller.getDuration();
|
||||
|
||||
if (media != null && media.getChapters() != null) {
|
||||
setHasChapters(media.getChapters().size() > 0);
|
||||
currentChapterIndex = ChapterUtils.getCurrentChapterIndex(media, controller.getPosition());
|
||||
} else {
|
||||
setHasChapters(false);
|
||||
currentChapterIndex = -1;
|
||||
}
|
||||
updatePosition(new PlaybackPositionEvent(controller.getPosition(), duration));
|
||||
updatePlaybackSpeedButton(media);
|
||||
setChapterDividers(media);
|
||||
@ -472,6 +435,7 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
int currentPosition = converter.convert(event.getPosition());
|
||||
int duration = converter.convert(event.getDuration());
|
||||
int remainingTime = converter.convert(Math.max(event.getDuration() - event.getPosition(), 0));
|
||||
currentChapterIndex = ChapterUtils.getCurrentChapterIndex(controller.getMedia(), currentPosition);
|
||||
Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
|
||||
if (currentPosition == PlaybackService.INVALID_TIME || duration == PlaybackService.INVALID_TIME) {
|
||||
Log.w(TAG, "Could not react to position observer update because of invalid time");
|
||||
@ -620,14 +584,13 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
@Override
|
||||
public Fragment createFragment(int position) {
|
||||
Log.d(TAG, "getItem(" + position + ")");
|
||||
|
||||
switch (position) {
|
||||
case POS_COVER:
|
||||
return new CoverFragment();
|
||||
case POS_SHOWNOTES:
|
||||
return new ItemDescriptionFragment();
|
||||
default:
|
||||
case POS_CHAPTERS:
|
||||
return new ChaptersFragment();
|
||||
case POS_DESCRIPTION:
|
||||
return new ItemDescriptionFragment();
|
||||
}
|
||||
}
|
||||
|
||||
@ -636,4 +599,21 @@ public class AudioPlayerFragment extends Fragment implements
|
||||
return NUM_CONTENT_FRAGMENTS;
|
||||
}
|
||||
}
|
||||
|
||||
public void scrollToPage(int page, boolean smoothScroll) {
|
||||
if (pager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
pager.setCurrentItem(page, smoothScroll);
|
||||
|
||||
Fragment visibleChild = getChildFragmentManager().findFragmentByTag("f" + POS_DESCRIPTION);
|
||||
if (visibleChild instanceof ItemDescriptionFragment) {
|
||||
((ItemDescriptionFragment) visibleChild).scrollToTop();
|
||||
}
|
||||
}
|
||||
|
||||
public void scrollToPage(int page) {
|
||||
scrollToPage(page, false);
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +1,65 @@
|
||||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatDialogFragment;
|
||||
import androidx.recyclerview.widget.DividerItemDecoration;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.adapter.ChaptersListAdapter;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.model.feed.Chapter;
|
||||
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
||||
import de.danoeh.antennapod.core.util.ChapterUtils;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.view.EmptyViewHandler;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
public class ChaptersFragment extends Fragment {
|
||||
private static final String TAG = "ChaptersFragment";
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.adapter.ChaptersListAdapter;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.core.service.playback.PlayerStatus;
|
||||
import de.danoeh.antennapod.core.util.ChapterUtils;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.model.feed.Chapter;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
||||
public class ChaptersFragment extends AppCompatDialogFragment {
|
||||
public static final String TAG = "ChaptersFragment";
|
||||
private ChaptersListAdapter adapter;
|
||||
private PlaybackController controller;
|
||||
private Disposable disposable;
|
||||
private int focusedChapter = -1;
|
||||
private Playable media;
|
||||
private LinearLayoutManager layoutManager;
|
||||
private ProgressBar progressBar;
|
||||
|
||||
@Nullable
|
||||
@NonNull
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View root = inflater.inflate(R.layout.simple_list_fragment, container, false);
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
return new AlertDialog.Builder(requireContext())
|
||||
.setTitle(getString(R.string.chapters_label))
|
||||
.setView(onCreateView(getLayoutInflater()))
|
||||
.setNegativeButton(getString(R.string.cancel_label), null) //dismisses
|
||||
.create();
|
||||
}
|
||||
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater) {
|
||||
View root = inflater.inflate(R.layout.simple_list_fragment, null, false);
|
||||
root.findViewById(R.id.toolbar).setVisibility(View.GONE);
|
||||
RecyclerView recyclerView = root.findViewById(R.id.recyclerView);
|
||||
progressBar = root.findViewById(R.id.progLoading);
|
||||
layoutManager = new LinearLayoutManager(getActivity());
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(),
|
||||
@ -58,11 +75,11 @@ public class ChaptersFragment extends Fragment {
|
||||
});
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
EmptyViewHandler emptyView = new EmptyViewHandler(getContext());
|
||||
emptyView.attachToRecyclerView(recyclerView);
|
||||
emptyView.setIcon(R.drawable.ic_bookmark);
|
||||
emptyView.setTitle(R.string.no_chapters_head_label);
|
||||
emptyView.setMessage(R.string.no_chapters_label);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
|
||||
RelativeLayout.LayoutParams wrapHeight = new RelativeLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
recyclerView.setLayoutParams(wrapHeight);
|
||||
|
||||
return root;
|
||||
}
|
||||
@ -136,6 +153,11 @@ public class ChaptersFragment extends Fragment {
|
||||
if (adapter == null) {
|
||||
return;
|
||||
}
|
||||
if (media.getChapters() != null && media.getChapters().size() <= 0) {
|
||||
dismiss();
|
||||
} else {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
}
|
||||
adapter.setMedia(media);
|
||||
int positionOfCurrentChapter = getCurrentChapter(media);
|
||||
updateChapterSelection(positionOfCurrentChapter);
|
||||
|
@ -1,7 +1,9 @@
|
||||
package de.danoeh.antennapod.fragment;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
@ -10,17 +12,29 @@ import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Space;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.graphics.BlendModeColorFilterCompat;
|
||||
import androidx.core.graphics.BlendModeCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.load.resource.bitmap.FitCenter;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
|
||||
import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
|
||||
@ -28,16 +42,13 @@ import de.danoeh.antennapod.core.glide.ApGlideSettings;
|
||||
import de.danoeh.antennapod.core.util.ChapterUtils;
|
||||
import de.danoeh.antennapod.core.util.DateUtils;
|
||||
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.model.feed.Chapter;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
|
||||
/**
|
||||
* Displays the cover and the title of a FeedItem.
|
||||
@ -51,9 +62,16 @@ public class CoverFragment extends Fragment {
|
||||
private TextView txtvPodcastTitle;
|
||||
private TextView txtvEpisodeTitle;
|
||||
private ImageView imgvCover;
|
||||
private LinearLayout openDescription;
|
||||
private Space counterweight;
|
||||
private Space spacer;
|
||||
private ImageButton butPrevChapter;
|
||||
private ImageButton butNextChapter;
|
||||
private LinearLayout episodeDetails;
|
||||
private LinearLayout chapterControl;
|
||||
private PlaybackController controller;
|
||||
private Disposable disposable;
|
||||
private int displayedChapterIndex = -2;
|
||||
private int displayedChapterIndex = -1;
|
||||
private Playable media;
|
||||
|
||||
@Override
|
||||
@ -64,7 +82,30 @@ public class CoverFragment extends Fragment {
|
||||
txtvPodcastTitle = root.findViewById(R.id.txtvPodcastTitle);
|
||||
txtvEpisodeTitle = root.findViewById(R.id.txtvEpisodeTitle);
|
||||
imgvCover = root.findViewById(R.id.imgvCover);
|
||||
episodeDetails = root.findViewById(R.id.episode_details);
|
||||
final ImageView descriptionIcon = root.findViewById(R.id.description_icon);
|
||||
chapterControl = root.findViewById(R.id.chapterButton);
|
||||
butPrevChapter = root.findViewById(R.id.butPrevChapter);
|
||||
butNextChapter = root.findViewById(R.id.butNextChapter);
|
||||
|
||||
imgvCover.setOnClickListener(v -> onPlayPause());
|
||||
openDescription = root.findViewById(R.id.openDescription);
|
||||
counterweight = root.findViewById(R.id.counterweight);
|
||||
spacer = root.findViewById(R.id.details_spacer);
|
||||
View.OnClickListener scrollToDesc = view ->
|
||||
((AudioPlayerFragment) requireParentFragment()).scrollToPage(AudioPlayerFragment.POS_DESCRIPTION, true);
|
||||
openDescription.setOnClickListener(scrollToDesc);
|
||||
ColorFilter colorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
|
||||
txtvPodcastTitle.getCurrentTextColor(), BlendModeCompat.SRC_IN);
|
||||
butNextChapter.setColorFilter(colorFilter);
|
||||
butPrevChapter.setColorFilter(colorFilter);
|
||||
descriptionIcon.setColorFilter(colorFilter);
|
||||
ChaptersFragment chaptersFragment = new ChaptersFragment();
|
||||
chapterControl.setOnClickListener(v ->
|
||||
chaptersFragment.show(getChildFragmentManager(), ChaptersFragment.TAG));
|
||||
butPrevChapter.setOnClickListener(v -> seekToPrevChapter());
|
||||
butNextChapter.setOnClickListener(v -> seekToNextChapter());
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
@ -80,6 +121,7 @@ public class CoverFragment extends Fragment {
|
||||
disposable = Maybe.<Playable>create(emitter -> {
|
||||
Playable media = controller.getMedia();
|
||||
if (media != null) {
|
||||
ChapterUtils.loadChapters(media, getContext());
|
||||
emitter.onSuccess(media);
|
||||
} else {
|
||||
emitter.onComplete();
|
||||
@ -100,8 +142,73 @@ public class CoverFragment extends Fragment {
|
||||
+ "\u00A0"
|
||||
+ StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0"));
|
||||
txtvEpisodeTitle.setText(media.getEpisodeTitle());
|
||||
displayedChapterIndex = -2; // Force refresh
|
||||
displayCoverImage(media.getPosition());
|
||||
displayedChapterIndex = -1;
|
||||
refreshChapterData(ChapterUtils.getCurrentChapterIndex(media, media.getPosition())); //calls displayCoverImage
|
||||
updateChapterControlVisibility();
|
||||
}
|
||||
|
||||
private void updateChapterControlVisibility() {
|
||||
if (media.getChapters() != null) {
|
||||
boolean chapterControlVisible = media.getChapters().size() > 0;
|
||||
int newVisibility = chapterControlVisible ? View.VISIBLE : View.GONE;
|
||||
if (chapterControl.getVisibility() != newVisibility) {
|
||||
chapterControl.setVisibility(newVisibility);
|
||||
ObjectAnimator.ofFloat(chapterControl,
|
||||
"alpha",
|
||||
chapterControlVisible ? 0 : 1,
|
||||
chapterControlVisible ? 1 : 0)
|
||||
.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshChapterData(int chapterIndex) {
|
||||
if (chapterIndex > -1) {
|
||||
if (media.getPosition() > media.getDuration() || chapterIndex >= media.getChapters().size() - 1) {
|
||||
displayedChapterIndex = media.getChapters().size() - 1;
|
||||
butNextChapter.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
displayedChapterIndex = chapterIndex;
|
||||
butNextChapter.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
displayCoverImage();
|
||||
}
|
||||
|
||||
private Chapter getCurrentChapter() {
|
||||
if (media == null || media.getChapters() == null || displayedChapterIndex == -1) {
|
||||
return null;
|
||||
}
|
||||
return media.getChapters().get(displayedChapterIndex);
|
||||
}
|
||||
|
||||
private void seekToPrevChapter() {
|
||||
Chapter curr = getCurrentChapter();
|
||||
|
||||
if (controller == null || curr == null || displayedChapterIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (displayedChapterIndex < 1) {
|
||||
controller.seekTo(0);
|
||||
} else if ((controller.getPosition() - 10000 * controller.getCurrentPlaybackSpeedMultiplier())
|
||||
< curr.getStart()) {
|
||||
refreshChapterData(displayedChapterIndex - 1);
|
||||
controller.seekToChapter(media.getChapters().get(displayedChapterIndex));
|
||||
} else {
|
||||
controller.seekToChapter(curr);
|
||||
}
|
||||
}
|
||||
|
||||
private void seekToNextChapter() {
|
||||
if (controller == null || media == null || media.getChapters() == null
|
||||
|| displayedChapterIndex == -1 || displayedChapterIndex + 1 >= media.getChapters().size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
refreshChapterData(displayedChapterIndex + 1);
|
||||
controller.seekToChapter(media.getChapters().get(displayedChapterIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -139,22 +246,18 @@ public class CoverFragment extends Fragment {
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(PlaybackPositionEvent event) {
|
||||
if (media == null) {
|
||||
return;
|
||||
int newChapterIndex = ChapterUtils.getCurrentChapterIndex(media, event.getPosition());
|
||||
if (newChapterIndex > -1 && newChapterIndex != displayedChapterIndex) {
|
||||
refreshChapterData(newChapterIndex);
|
||||
}
|
||||
displayCoverImage(event.getPosition());
|
||||
}
|
||||
|
||||
private void displayCoverImage(int position) {
|
||||
int chapter = ChapterUtils.getCurrentChapterIndex(media, position);
|
||||
if (chapter != displayedChapterIndex) {
|
||||
displayedChapterIndex = chapter;
|
||||
|
||||
RequestOptions options = new RequestOptions()
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.dontAnimate()
|
||||
.transforms(new FitCenter(),
|
||||
new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density)));
|
||||
private void displayCoverImage() {
|
||||
RequestOptions options = new RequestOptions()
|
||||
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
|
||||
.dontAnimate()
|
||||
.transforms(new FitCenter(),
|
||||
new RoundedCorners((int) (16 * getResources().getDisplayMetrics().density)));
|
||||
|
||||
RequestBuilder<Drawable> cover = Glide.with(this)
|
||||
.load(media.getImageLocation())
|
||||
@ -163,16 +266,16 @@ public class CoverFragment extends Fragment {
|
||||
.apply(options))
|
||||
.apply(options);
|
||||
|
||||
if (chapter == -1 || TextUtils.isEmpty(media.getChapters().get(chapter).getImageUrl())) {
|
||||
cover.into(imgvCover);
|
||||
} else {
|
||||
Glide.with(this)
|
||||
.load(EmbeddedChapterImage.getModelFor(media, chapter))
|
||||
.apply(options)
|
||||
.thumbnail(cover)
|
||||
.error(cover)
|
||||
.into(imgvCover);
|
||||
}
|
||||
if (displayedChapterIndex == -1 || media == null || media.getChapters() == null
|
||||
|| TextUtils.isEmpty(media.getChapters().get(displayedChapterIndex).getImageUrl())) {
|
||||
cover.into(imgvCover);
|
||||
} else {
|
||||
Glide.with(this)
|
||||
.load(EmbeddedChapterImage.getModelFor(media, displayedChapterIndex))
|
||||
.apply(options)
|
||||
.thumbnail(cover)
|
||||
.error(cover)
|
||||
.into(imgvCover);
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,6 +299,10 @@ public class CoverFragment extends Fragment {
|
||||
LinearLayout.LayoutParams textParams = (LinearLayout.LayoutParams) textContainer.getLayoutParams();
|
||||
double ratio = (float) newConfig.screenHeightDp / (float) newConfig.screenWidthDp;
|
||||
|
||||
boolean spacerVisible = true;
|
||||
ViewGroup detailsParent = (ViewGroup) getView();
|
||||
int detailsWidth = ViewGroup.LayoutParams.MATCH_PARENT;
|
||||
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
double percentageWidth = 0.8;
|
||||
if (ratio <= SIXTEEN_BY_NINE) {
|
||||
@ -217,6 +324,26 @@ public class CoverFragment extends Fragment {
|
||||
textParams.weight = 1;
|
||||
imgvCover.setLayoutParams(params);
|
||||
}
|
||||
|
||||
spacerVisible = false;
|
||||
detailsParent = textContainer;
|
||||
detailsWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
}
|
||||
|
||||
if (displayedChapterIndex == -1) {
|
||||
detailsWidth = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
}
|
||||
|
||||
spacer.setVisibility(spacerVisible ? View.VISIBLE : View.GONE);
|
||||
counterweight.setVisibility(spacerVisible ? View.VISIBLE : View.GONE);
|
||||
LinearLayout.LayoutParams wrapHeight =
|
||||
new LinearLayout.LayoutParams(detailsWidth, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
episodeDetails.setLayoutParams(wrapHeight);
|
||||
getView().findViewById(R.id.vertical_divider).setVisibility(spacerVisible ? View.GONE : View.VISIBLE);
|
||||
|
||||
if (episodeDetails.getParent() != detailsParent) {
|
||||
((ViewGroup) episodeDetails.getParent()).removeView(episodeDetails);
|
||||
detailsParent.addView(episodeDetails);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,13 +8,15 @@ import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.core.storage.DBReader;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.danoeh.antennapod.core.util.playback.PlaybackController;
|
||||
import de.danoeh.antennapod.core.util.playback.Timeline;
|
||||
import de.danoeh.antennapod.model.feed.FeedMedia;
|
||||
import de.danoeh.antennapod.model.playback.Playable;
|
||||
import de.danoeh.antennapod.view.ShownotesWebView;
|
||||
import io.reactivex.Maybe;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
@ -143,14 +145,18 @@ public class ItemDescriptionFragment extends Fragment {
|
||||
&& id.equals(controller.getMedia().getIdentifier().toString())
|
||||
&& webvDescription != null) {
|
||||
Log.d(TAG, "Restored scroll Position: " + scrollY);
|
||||
webvDescription.scrollTo(webvDescription.getScrollX(),
|
||||
scrollY);
|
||||
webvDescription.scrollTo(webvDescription.getScrollX(), scrollY);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void scrollToTop() {
|
||||
webvDescription.scrollTo(0, 0);
|
||||
savePreference();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
@ -20,92 +20,181 @@
|
||||
package de.danoeh.antennapod.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.ViewParent;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import de.danoeh.antennapod.R;
|
||||
|
||||
import static androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL;
|
||||
import static androidx.viewpager2.widget.ViewPager2.ORIENTATION_VERTICAL;
|
||||
|
||||
|
||||
/**
|
||||
* Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
|
||||
* where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
|
||||
* ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
|
||||
*
|
||||
* <p>This solution has limitations when using multiple levels of nested scrollable elements
|
||||
* This solution has limitations when using multiple levels of nested scrollable elements
|
||||
* (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
|
||||
*/
|
||||
class NestedScrollableHost extends FrameLayout {
|
||||
*/ // KhaledAlharthi/NestedScrollableHost.java
|
||||
public class NestedScrollableHost extends FrameLayout {
|
||||
|
||||
private ViewPager2 parentViewPager;
|
||||
private int touchSlop = 0;
|
||||
private float initialX = 0f;
|
||||
private float initialY = 0f;
|
||||
private int preferVertical = 1;
|
||||
private int preferHorizontal = 1;
|
||||
private int scrollDirection = 0;
|
||||
|
||||
public NestedScrollableHost(@NonNull Context context) {
|
||||
super(context);
|
||||
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
init(context);
|
||||
}
|
||||
|
||||
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
setAttributes(context, attrs);
|
||||
}
|
||||
|
||||
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
setAttributes(context, attrs);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public NestedScrollableHost(@NonNull Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
init(context);
|
||||
setAttributes(context, attrs);
|
||||
}
|
||||
|
||||
private void setAttributes(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(
|
||||
attrs,
|
||||
R.styleable.NestedScrollableHost,
|
||||
0, 0);
|
||||
|
||||
try {
|
||||
preferHorizontal = a.getInteger(R.styleable.NestedScrollableHost_preferHorizontal, 1);
|
||||
preferVertical = a.getInteger(R.styleable.NestedScrollableHost_preferVertical, 1);
|
||||
scrollDirection = a.getInteger(R.styleable.NestedScrollableHost_scrollDirection, 0);
|
||||
} finally {
|
||||
a.recycle();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void init(Context context) {
|
||||
touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
|
||||
|
||||
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
|
||||
@Override
|
||||
public boolean onPreDraw() {
|
||||
View v = (View) getParent();
|
||||
while (v != null && !(v instanceof ViewPager2) || isntSameDirection(v)) {
|
||||
v = (View) v.getParent();
|
||||
}
|
||||
parentViewPager = (ViewPager2) v;
|
||||
|
||||
getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private int touchSlop;
|
||||
private float initialX = 0f;
|
||||
private float initialY = 0f;
|
||||
|
||||
private ViewPager2 getParentViewPager() {
|
||||
View v = (View) getParent();
|
||||
while (v != null && !(v instanceof ViewPager2)) {
|
||||
v = (View) v.getParent();
|
||||
private Boolean isntSameDirection(View v) {
|
||||
int orientation = 0;
|
||||
switch (scrollDirection) {
|
||||
default:
|
||||
case 0:
|
||||
return false;
|
||||
case 1:
|
||||
orientation = ORIENTATION_VERTICAL;
|
||||
break;
|
||||
case 2:
|
||||
orientation = ORIENTATION_HORIZONTAL;
|
||||
break;
|
||||
}
|
||||
return v == null ? null : (ViewPager2) v;
|
||||
return ((v instanceof ViewPager2) && ((ViewPager2) v).getOrientation() != orientation);
|
||||
}
|
||||
|
||||
public boolean onInterceptTouchEvent(MotionEvent e) {
|
||||
ViewPager2 parentViewPager = getParentViewPager();
|
||||
|
||||
@Override
|
||||
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||
handleInterceptTouchEvent(ev);
|
||||
return super.onInterceptTouchEvent(ev);
|
||||
}
|
||||
|
||||
|
||||
private boolean canChildScroll(int orientation, float delta) {
|
||||
int direction = (int) -delta;
|
||||
View child = getChildAt(0);
|
||||
if (orientation == 0) {
|
||||
return child.canScrollHorizontally(direction);
|
||||
} else if (orientation == 1) {
|
||||
return child.canScrollVertically(direction);
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInterceptTouchEvent(MotionEvent e) {
|
||||
if (parentViewPager == null) {
|
||||
return super.onInterceptTouchEvent(e);
|
||||
return;
|
||||
}
|
||||
int orientation = parentViewPager.getOrientation();
|
||||
boolean preferedDirection = preferHorizontal + preferVertical > 2;
|
||||
|
||||
// Early return if child can't scroll in same direction as parent and theres no prefered scroll direction
|
||||
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f) && !preferedDirection) {
|
||||
return;
|
||||
}
|
||||
|
||||
ViewParent parent = getParent();
|
||||
int orientation = parentViewPager.getOrientation();
|
||||
|
||||
if (e.getAction() == MotionEvent.ACTION_DOWN) {
|
||||
initialX = e.getX();
|
||||
initialY = e.getY();
|
||||
parent.requestDisallowInterceptTouchEvent(true);
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
} else if (e.getAction() == MotionEvent.ACTION_MOVE) {
|
||||
int dx = (int) (e.getX() - initialX);
|
||||
int dy = (int) (e.getY() - initialY);
|
||||
boolean isVpHorizontal = orientation == ORIENTATION_HORIZONTAL;
|
||||
float dx = e.getX() - initialX;
|
||||
float dy = e.getY() - initialY;
|
||||
boolean isVpHorizontal = orientation == ViewPager2.ORIENTATION_HORIZONTAL;
|
||||
|
||||
// assuming ViewPager2 touch-slop is 2x touch-slop of child
|
||||
float scaledDx = Math.abs(dx) * (isVpHorizontal ? .5f : 1f);
|
||||
float scaledDy = Math.abs(dy) * (isVpHorizontal ? 1f : .5f);
|
||||
float scaledDx = Math.abs(dx) * (isVpHorizontal ? 1f : 0.5f) * preferHorizontal;
|
||||
float scaledDy = Math.abs(dy) * (isVpHorizontal ? 0.5f : 1f) * preferVertical;
|
||||
if (scaledDx > touchSlop || scaledDy > touchSlop) {
|
||||
int value = isVpHorizontal ? dy : dx;
|
||||
if (isVpHorizontal == (scaledDy > scaledDx)) {
|
||||
// Gesture is perpendicular
|
||||
orientation = orientation == ORIENTATION_VERTICAL
|
||||
? ORIENTATION_HORIZONTAL : ORIENTATION_VERTICAL;
|
||||
value = isVpHorizontal ? dy : dx;
|
||||
}
|
||||
|
||||
int direction = (int) -Math.copySign(1, value);
|
||||
View child = getChildAt(0);
|
||||
if (orientation == ORIENTATION_HORIZONTAL) {
|
||||
parent.requestDisallowInterceptTouchEvent(child.canScrollHorizontally(direction));
|
||||
// Gesture is perpendicular, allow all parents to intercept
|
||||
getParent().requestDisallowInterceptTouchEvent(preferedDirection);
|
||||
} else {
|
||||
parent.requestDisallowInterceptTouchEvent(child.canScrollVertically(direction));
|
||||
// Gesture is parallel, query child if movement in that direction is possible
|
||||
if (canChildScroll(orientation, isVpHorizontal ? dx : dy)) {
|
||||
// Child can scroll, disallow all parents to intercept
|
||||
getParent().requestDisallowInterceptTouchEvent(true);
|
||||
} else {
|
||||
// Child cannot scroll, allow all parents to intercept
|
||||
getParent().requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return super.onInterceptTouchEvent(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
29
app/src/main/res/drawable-v21/grey_border.xml
Normal file
29
app/src/main/res/drawable-v21/grey_border.xml
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="?attr/colorControlHighlight">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape
|
||||
android:shape="rectangle">
|
||||
<corners
|
||||
android:radius="8dp"
|
||||
android:topRightRadius="8dp"
|
||||
android:bottomRightRadius="8dp"
|
||||
android:bottomLeftRadius="8dp" />
|
||||
<solid android:color="@android:color/white"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape
|
||||
android:shape="rectangle">
|
||||
<corners
|
||||
android:radius="8dp"
|
||||
android:topRightRadius="8dp"
|
||||
android:bottomRightRadius="8dp"
|
||||
android:bottomLeftRadius="8dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?android:attr/textColorSecondary" />
|
||||
<solid android:color="@android:color/transparent"/>
|
||||
</shape>
|
||||
</item>
|
||||
</ripple>
|
21
app/src/main/res/drawable/grey_border.xml
Normal file
21
app/src/main/res/drawable/grey_border.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector>
|
||||
|
||||
<item>
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners
|
||||
android:radius="8dp"
|
||||
android:topRightRadius="8dp"
|
||||
android:bottomRightRadius="8dp"
|
||||
android:bottomLeftRadius="8dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@android:color/darker_gray" />
|
||||
<solid android:color="@android:color/transparent"/>
|
||||
</shape>
|
||||
|
||||
</item>
|
||||
|
||||
</selector>
|
@ -15,16 +15,6 @@
|
||||
app:navigationIcon="?homeAsUpIndicator"
|
||||
android:id="@+id/toolbar"/>
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/sliding_tabs"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:background="?android:attr/windowBackground"
|
||||
app:tabBackground="?attr/selectableItemBackground"
|
||||
app:tabMode="fixed"
|
||||
app:tabGravity="fill"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/playerFragment"
|
||||
android:layout_width="match_parent"
|
||||
@ -39,9 +29,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_above="@id/playtime_layout"
|
||||
android:layout_below="@id/sliding_tabs"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:foreground="?android:windowContentOverlay"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
android:orientation="vertical" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:squareImageView="http://schemas.android.com/apk/de.danoeh.antennapod"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
@ -10,14 +11,18 @@
|
||||
android:padding="8dp"
|
||||
android:gravity="center">
|
||||
|
||||
<Space
|
||||
android:id="@+id/counterweight"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<de.danoeh.antennapod.ui.common.SquareImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_weight="0"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless"
|
||||
android:importantForAccessibility="no"
|
||||
@ -29,9 +34,9 @@
|
||||
android:id="@+id/cover_fragment_text_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp">
|
||||
android:layout_marginVertical="8dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPodcastTitle"
|
||||
@ -40,9 +45,9 @@
|
||||
android:ellipsize="none"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="2"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="Podcast" />
|
||||
|
||||
<TextView
|
||||
@ -51,11 +56,114 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="none"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:maxLines="2"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="Episode" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/vertical_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Space
|
||||
android:id="@+id/details_spacer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/episode_details"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:layout_weight="0"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/openDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:background="@drawable/grey_border"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:minWidth="150dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/description_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:contentDescription="@string/shownotes_contentdescription"
|
||||
android:padding="2dp"
|
||||
app:srcCompat="@drawable/ic_info" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/shownotes_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="none"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="2"
|
||||
android:text="@string/shownotes_label"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chapterButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/grey_border"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:minWidth="150dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butPrevChapter"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/prev_chapter"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_chapter_prev" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chapters_label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/chapters_label"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_navdrawer" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butNextChapter"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/next_chapter"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_chapter_next" />
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/content_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
@ -185,7 +186,8 @@
|
||||
<de.danoeh.antennapod.view.NestedScrollableHost
|
||||
android:layout_below="@id/header"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
app:preferVertical="3">
|
||||
|
||||
<de.danoeh.antennapod.view.ShownotesWebView
|
||||
android:id="@+id/webvDescription"
|
||||
|
@ -1,18 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<de.danoeh.antennapod.view.NestedScrollableHost xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fillViewport="false">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:fillViewport="false"
|
||||
app:preferVertical="10"
|
||||
android:nestedScrollingEnabled="true">
|
||||
|
||||
<de.danoeh.antennapod.view.NestedScrollableHost
|
||||
<de.danoeh.antennapod.view.ShownotesWebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<de.danoeh.antennapod.view.ShownotesWebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</de.danoeh.antennapod.view.NestedScrollableHost>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</de.danoeh.antennapod.view.NestedScrollableHost>
|
12
app/src/main/res/values/attrs.xml
Normal file
12
app/src/main/res/values/attrs.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<declare-styleable name="NestedScrollableHost">
|
||||
<attr name="scrollDirection" format="enum">
|
||||
<enum name="both" value="0"/>
|
||||
<enum name="vertical" value="1"/>
|
||||
<enum name="horizontal" value="2"/>
|
||||
</attr>
|
||||
<attr name="preferHorizontal" format="integer"/>
|
||||
<attr name="preferVertical" format="integer"/>
|
||||
</declare-styleable>
|
||||
</resources>
|
@ -1,5 +0,0 @@
|
||||
<vector android:height="24dp"
|
||||
android:viewportHeight="24.0" android:viewportWidth="24.0"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/action_icon_color" android:pathData="M17,3H7c-1.1,0 -1.99,0.9 -1.99,2L5,21l7,-3 7,3V5c0,-1.1 -0.9,-2 -2,-2z"/>
|
||||
</vector>
|
9
core/src/main/res/drawable/ic_chapter_next.xml
Normal file
9
core/src/main/res/drawable/ic_chapter_next.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/action_icon_color"
|
||||
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6 -6,-6z"/>
|
||||
</vector>
|
9
core/src/main/res/drawable/ic_chapter_prev.xml
Normal file
9
core/src/main/res/drawable/ic_chapter_prev.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/action_icon_color"
|
||||
android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
|
||||
</vector>
|
@ -87,7 +87,6 @@
|
||||
<string name="url_label">URL</string>
|
||||
<string name="support_funding_label">Support</string>
|
||||
<string name="support_podcast">Support this Podcast</string>
|
||||
<string name="cover_label">Cover</string>
|
||||
<string name="error_label">Error</string>
|
||||
<string name="error_msg_prefix">An error occurred:</string>
|
||||
<string name="refresh_label">Refresh</string>
|
||||
@ -96,6 +95,7 @@
|
||||
<string name="chapter_duration">Duration: %1$s</string>
|
||||
<string name="description_label">Description</string>
|
||||
<string name="shownotes_label">Shownotes</string>
|
||||
<string name="shownotes_contentdescription">swipe up to read shownotes</string>
|
||||
<string name="episodes_suffix">\u0020episodes</string>
|
||||
<string name="processing_label">Processing</string>
|
||||
<string name="close_label">Close</string>
|
||||
@ -342,8 +342,6 @@
|
||||
<string name="no_new_episodes_label">When new episodes arrive, they will be shown here.</string>
|
||||
<string name="no_fav_episodes_head_label">No favorite episodes</string>
|
||||
<string name="no_fav_episodes_label">You can add episodes to the favorites by long-pressing them.</string>
|
||||
<string name="no_chapters_head_label">No chapters</string>
|
||||
<string name="no_chapters_label">This episode has no chapters.</string>
|
||||
<string name="no_subscriptions_head_label">No subscriptions</string>
|
||||
<string name="no_subscriptions_label">To subscribe to a podcast, press the plus icon below.</string>
|
||||
|
||||
@ -681,6 +679,8 @@
|
||||
<string name="position">Position: %1$s</string>
|
||||
<string name="apply_action">Apply action</string>
|
||||
<string name="play_chapter">Play chapter</string>
|
||||
<string name="prev_chapter">Previous chapter</string>
|
||||
<string name="next_chapter">Next chapter</string>
|
||||
|
||||
<!-- Feed settings/information screen -->
|
||||
<string name="authentication_label">Authentication</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user