mirror of
https://github.com/mediathekview/zapp.git
synced 2024-09-19 20:02:17 +02:00
Convert StreamPageFragment to kotlin
This commit is contained in:
parent
1b7d1f4ac1
commit
94f86af588
@ -2,9 +2,8 @@
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/2016-09_Zapp.iml" filepath="$PROJECT_DIR$/.idea/modules/2016-09_Zapp.iml" group="2016-09_Zapp" />
|
||||
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" group="2016-09_Zapp/app" />
|
||||
<module fileurl="file://$PROJECT_DIR$/zapp.iml" filepath="$PROJECT_DIR$/zapp.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/2016-09_Zapp.iml" filepath="$PROJECT_DIR$/.idea/modules/2016-09_Zapp.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/app/2016-09_Zapp.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/2016-09_Zapp.app.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -1,386 +0,0 @@
|
||||
package de.christinecoenen.code.zapp.app.livestream.ui.detail;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import de.christinecoenen.code.zapp.R;
|
||||
import de.christinecoenen.code.zapp.app.ZappApplicationBase;
|
||||
import de.christinecoenen.code.zapp.app.livestream.ui.views.ProgramInfoViewBase;
|
||||
import de.christinecoenen.code.zapp.app.player.BackgroundPlayerService;
|
||||
import de.christinecoenen.code.zapp.app.player.Player;
|
||||
import de.christinecoenen.code.zapp.app.player.VideoInfo;
|
||||
import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository;
|
||||
import de.christinecoenen.code.zapp.databinding.ActivityChannelDetailBinding;
|
||||
import de.christinecoenen.code.zapp.models.channels.ChannelModel;
|
||||
import de.christinecoenen.code.zapp.models.channels.IChannelList;
|
||||
import de.christinecoenen.code.zapp.utils.system.MultiWindowHelper;
|
||||
import de.christinecoenen.code.zapp.utils.system.ShortcutHelper;
|
||||
import de.christinecoenen.code.zapp.utils.video.SwipeablePlayerView;
|
||||
import de.christinecoenen.code.zapp.utils.view.ClickableViewPager;
|
||||
import de.christinecoenen.code.zapp.utils.view.ColorHelper;
|
||||
import de.christinecoenen.code.zapp.utils.view.FullscreenActivity;
|
||||
import io.reactivex.disposables.CompositeDisposable;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class ChannelDetailActivity extends FullscreenActivity implements StreamPageFragment.Listener {
|
||||
|
||||
private static final String EXTRA_CHANNEL_ID = "de.christinecoenen.code.zapp.EXTRA_CHANNEL_ID";
|
||||
|
||||
private Toolbar toolbar;
|
||||
private ClickableViewPager viewPager;
|
||||
private SwipeablePlayerView videoView;
|
||||
private ProgressBar progressView;
|
||||
private ProgramInfoViewBase programInfoView;
|
||||
|
||||
private int playStreamDelayMillis;
|
||||
|
||||
private final Handler playHandler = new Handler();
|
||||
private ChannelDetailAdapter channelDetailAdapter;
|
||||
private ChannelModel currentChannel;
|
||||
private Window window;
|
||||
private IChannelList channelList;
|
||||
private Player player;
|
||||
private BackgroundPlayerService.Binder binder;
|
||||
private SettingsRepository settings;
|
||||
private final CompositeDisposable disposable = new CompositeDisposable();
|
||||
|
||||
private final Runnable playRunnable = this::play;
|
||||
|
||||
private final ChannelDetailAdapter.Listener channelDetailListener =
|
||||
new ChannelDetailAdapter.Listener() {
|
||||
@Override
|
||||
public void onItemSelected(ChannelModel channel) {
|
||||
currentChannel = channel;
|
||||
setTitle(channel.getName());
|
||||
setColor(channel.getColor());
|
||||
|
||||
playDelayed();
|
||||
programInfoView.setChannel(channel);
|
||||
|
||||
ShortcutHelper.reportShortcutUsageGuarded(ChannelDetailActivity.this, channel.getId());
|
||||
}
|
||||
};
|
||||
|
||||
private final ViewPager.OnPageChangeListener onPageChangeListener =
|
||||
new ViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
|
||||
int color1 = channelDetailAdapter.getChannel(position).getColor();
|
||||
|
||||
if (positionOffset == 0) {
|
||||
setColor(color1);
|
||||
} else {
|
||||
int color2 = channelDetailAdapter.getChannel(position + 1).getColor();
|
||||
int color = ColorHelper.interpolate(positionOffset, color1, color2);
|
||||
setColor(color);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private final ServiceConnection backgroundPlayerServiceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder service) {
|
||||
binder = (BackgroundPlayerService.Binder) service;
|
||||
binder.setForegroundActivityIntent(getIntent());
|
||||
player = binder.getPlayer();
|
||||
player.setView(videoView);
|
||||
|
||||
Disposable bufferingDisposable = player.isBuffering()
|
||||
.subscribe(ChannelDetailActivity.this::onBufferingChanged, Timber::e);
|
||||
Disposable errorDisposable = player.getErrorResourceId()
|
||||
.subscribe(ChannelDetailActivity.this::onVideoError, Timber::e);
|
||||
disposable.addAll(bufferingDisposable, errorDisposable);
|
||||
|
||||
channelDetailListener.onItemSelected(currentChannel);
|
||||
|
||||
binder.movePlaybackToForeground();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
player.pause();
|
||||
}
|
||||
};
|
||||
|
||||
public static Intent getStartIntent(Context context, String channelId) {
|
||||
Intent intent = new Intent(context, ChannelDetailActivity.class);
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.putExtra(EXTRA_CHANNEL_ID, channelId);
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
ActivityChannelDetailBinding binding = ActivityChannelDetailBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
toolbar = binding.toolbar;
|
||||
viewPager = binding.viewpagerChannels;
|
||||
videoView = binding.video;
|
||||
programInfoView = binding.programInfo;
|
||||
progressView = binding.progressbarVideo;
|
||||
|
||||
playStreamDelayMillis = getResources().getInteger(R.integer.activity_channel_detail_play_stream_delay_millis);
|
||||
|
||||
setSupportActionBar(toolbar);
|
||||
window = getWindow();
|
||||
|
||||
if (getSupportActionBar() != null) {
|
||||
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
}
|
||||
|
||||
channelList = ((ZappApplicationBase) getApplication()).getChannelRepository().getChannelList();
|
||||
settings = new SettingsRepository(this);
|
||||
|
||||
// pager
|
||||
channelDetailAdapter = new ChannelDetailAdapter(
|
||||
getSupportFragmentManager(), channelList, channelDetailListener);
|
||||
viewPager.setAdapter(channelDetailAdapter);
|
||||
viewPager.addOnPageChangeListener(onPageChangeListener);
|
||||
viewPager.setOnClickListener(view -> contentView.performClick());
|
||||
viewPager.setOnTouchListener(this::onPagerTouch);
|
||||
videoView.setTouchOverlay(viewPager);
|
||||
|
||||
parseIntent(getIntent());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
// called when coming back from picture in picture mode
|
||||
parseIntent(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (MultiWindowHelper.isInsideMultiWindow(this)) {
|
||||
resumeActivity();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
if (!MultiWindowHelper.isInsideMultiWindow(this)) {
|
||||
resumeActivity();
|
||||
}
|
||||
|
||||
if (settings.getLockVideosInLandcapeFormat()) {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
|
||||
} else {
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (!MultiWindowHelper.isInsideMultiWindow(this)) {
|
||||
pauseActivity();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
pauseActivity();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode);
|
||||
if (isInPictureInPictureMode) {
|
||||
hide();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.activity_channel_detail, menu);
|
||||
|
||||
if (!MultiWindowHelper.supportsPictureInPictureMode(this)) {
|
||||
menu.removeItem(R.id.menu_pip);
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_share:
|
||||
startActivity(Intent.createChooser(currentChannel.getVideoShareIntent(), getString(R.string.action_share)));
|
||||
return true;
|
||||
case R.id.menu_play_in_background:
|
||||
binder.movePlaybackToBackground();
|
||||
finish();
|
||||
return true;
|
||||
case R.id.menu_pip:
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
enterPictureInPictureMode();
|
||||
}
|
||||
return true;
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
boolean handled = false;
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_LEFT:
|
||||
handled = prevChannel();
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_RIGHT:
|
||||
handled = nextChannel();
|
||||
break;
|
||||
}
|
||||
|
||||
return handled || super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
private void onVideoError(Integer messageResourceId) {
|
||||
if (messageResourceId == null || messageResourceId == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.pause();
|
||||
progressView.setVisibility(View.GONE);
|
||||
|
||||
if (channelDetailAdapter.getCurrentFragment() != null) {
|
||||
channelDetailAdapter.getCurrentFragment().onVideoError(getString(messageResourceId));
|
||||
}
|
||||
}
|
||||
|
||||
private void onBufferingChanged(boolean isBuffering) {
|
||||
if (isBuffering) {
|
||||
progressView.setVisibility(View.VISIBLE);
|
||||
} else if (!player.isIdle()) {
|
||||
progressView.setVisibility(View.INVISIBLE);
|
||||
channelDetailAdapter.getCurrentFragment().onVideoStart();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onErrorViewClicked() {
|
||||
player.recreate();
|
||||
player.resume();
|
||||
}
|
||||
|
||||
private boolean onPagerTouch(View view, MotionEvent motionEvent) {
|
||||
delayHide();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void parseIntent(Intent intent) {
|
||||
Bundle extras = intent.getExtras();
|
||||
//noinspection ConstantConditions
|
||||
String channelId = extras.getString(EXTRA_CHANNEL_ID);
|
||||
int channelPosition = channelList.indexOf(channelId);
|
||||
|
||||
viewPager.removeOnPageChangeListener(onPageChangeListener);
|
||||
viewPager.setCurrentItem(channelPosition);
|
||||
viewPager.addOnPageChangeListener(onPageChangeListener);
|
||||
}
|
||||
|
||||
private void pauseActivity() {
|
||||
disposable.clear();
|
||||
try {
|
||||
unbindService(backgroundPlayerServiceConnection);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void resumeActivity() {
|
||||
programInfoView.resume();
|
||||
BackgroundPlayerService.bind(this, backgroundPlayerServiceConnection);
|
||||
}
|
||||
|
||||
private void playDelayed() {
|
||||
if (player != null) {
|
||||
player.pause();
|
||||
}
|
||||
|
||||
playHandler.removeCallbacks(playRunnable);
|
||||
playHandler.postDelayed(playRunnable, playStreamDelayMillis);
|
||||
}
|
||||
|
||||
private void play() {
|
||||
if (currentChannel == null || binder == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent currentIntent = ChannelDetailActivity.getStartIntent(this, currentChannel.getId());
|
||||
binder.setForegroundActivityIntent(currentIntent);
|
||||
|
||||
Timber.d("play: %s", currentChannel.getName());
|
||||
player.load(VideoInfo.fromChannel(currentChannel));
|
||||
player.resume();
|
||||
}
|
||||
|
||||
private boolean prevChannel() {
|
||||
int nextItemIndex = viewPager.getCurrentItem() - 1;
|
||||
|
||||
if (nextItemIndex < 0) {
|
||||
return false;
|
||||
} else {
|
||||
delayHide();
|
||||
viewPager.setCurrentItem(nextItemIndex, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean nextChannel() {
|
||||
int nextItemIndex = viewPager.getCurrentItem() + 1;
|
||||
|
||||
if (nextItemIndex == channelDetailAdapter.getCount()) {
|
||||
return false;
|
||||
} else {
|
||||
delayHide();
|
||||
viewPager.setCurrentItem(nextItemIndex, true);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void setColor(int color) {
|
||||
progressView.getIndeterminateDrawable().setColorFilter(color, PorterDuff.Mode.SRC_IN);
|
||||
toolbar.setBackgroundColor(color);
|
||||
|
||||
int colorDarker = ColorHelper.darker(color, 0.075f);
|
||||
window.setStatusBarColor(colorDarker);
|
||||
|
||||
int colorAlpha = ColorHelper.darker(ColorHelper.withAlpha(color, 150), 0.25f);
|
||||
controlsView.setBackgroundColor(colorAlpha);
|
||||
}
|
||||
}
|
@ -0,0 +1,354 @@
|
||||
package de.christinecoenen.code.zapp.app.livestream.ui.detail
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
import android.view.*
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.viewpager.widget.ViewPager.OnPageChangeListener
|
||||
import androidx.viewpager.widget.ViewPager.SimpleOnPageChangeListener
|
||||
import de.christinecoenen.code.zapp.R
|
||||
import de.christinecoenen.code.zapp.app.ZappApplicationBase
|
||||
import de.christinecoenen.code.zapp.app.player.BackgroundPlayerService
|
||||
import de.christinecoenen.code.zapp.app.player.BackgroundPlayerService.Companion.bind
|
||||
import de.christinecoenen.code.zapp.app.player.Player
|
||||
import de.christinecoenen.code.zapp.app.player.VideoInfo.Companion.fromChannel
|
||||
import de.christinecoenen.code.zapp.app.settings.repository.SettingsRepository
|
||||
import de.christinecoenen.code.zapp.databinding.ActivityChannelDetailBinding
|
||||
import de.christinecoenen.code.zapp.models.channels.ChannelModel
|
||||
import de.christinecoenen.code.zapp.models.channels.IChannelList
|
||||
import de.christinecoenen.code.zapp.utils.system.MultiWindowHelper.isInsideMultiWindow
|
||||
import de.christinecoenen.code.zapp.utils.system.MultiWindowHelper.supportsPictureInPictureMode
|
||||
import de.christinecoenen.code.zapp.utils.system.ShortcutHelper.reportShortcutUsageGuarded
|
||||
import de.christinecoenen.code.zapp.utils.view.ColorHelper.darker
|
||||
import de.christinecoenen.code.zapp.utils.view.ColorHelper.interpolate
|
||||
import de.christinecoenen.code.zapp.utils.view.ColorHelper.withAlpha
|
||||
import de.christinecoenen.code.zapp.utils.view.FullscreenActivity
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import timber.log.Timber
|
||||
|
||||
class ChannelDetailActivity : FullscreenActivity(), StreamPageFragment.Listener {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val EXTRA_CHANNEL_ID = "de.christinecoenen.code.zapp.EXTRA_CHANNEL_ID"
|
||||
|
||||
@JvmStatic
|
||||
fun getStartIntent(context: Context?, channelId: String?): Intent {
|
||||
return Intent(context, ChannelDetailActivity::class.java).apply {
|
||||
action = Intent.ACTION_VIEW
|
||||
putExtra(EXTRA_CHANNEL_ID, channelId)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityChannelDetailBinding
|
||||
private lateinit var channelList: IChannelList
|
||||
private lateinit var settings: SettingsRepository
|
||||
private lateinit var channelDetailAdapter: ChannelDetailAdapter
|
||||
|
||||
private val disposable = CompositeDisposable()
|
||||
private val playRunnable = Runnable { play() }
|
||||
private val playHandler = Handler()
|
||||
|
||||
private var playStreamDelayMillis = 0
|
||||
private var currentChannel: ChannelModel? = null
|
||||
private var player: Player? = null
|
||||
private var binder: BackgroundPlayerService.Binder? = null
|
||||
|
||||
private val channelDetailListener: ChannelDetailAdapter.Listener = object : ChannelDetailAdapter.Listener {
|
||||
override fun onItemSelected(channel: ChannelModel) {
|
||||
currentChannel = channel
|
||||
title = channel.name
|
||||
|
||||
setColor(channel.color)
|
||||
playDelayed()
|
||||
|
||||
binding.programInfo.setChannel(channel)
|
||||
|
||||
reportShortcutUsageGuarded(this@ChannelDetailActivity, channel.id)
|
||||
}
|
||||
}
|
||||
|
||||
private val onPageChangeListener: OnPageChangeListener = object : SimpleOnPageChangeListener() {
|
||||
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
|
||||
val currentChannelColor = channelDetailAdapter.getChannel(position).color
|
||||
|
||||
if (positionOffset == 0f) {
|
||||
setColor(currentChannelColor)
|
||||
} else {
|
||||
val nextChannelColor = channelDetailAdapter.getChannel(position + 1).color
|
||||
val interpolatedColor = interpolate(positionOffset, currentChannelColor, nextChannelColor)
|
||||
setColor(interpolatedColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val backgroundPlayerServiceConnection: ServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(componentName: ComponentName, service: IBinder) {
|
||||
binder = service as BackgroundPlayerService.Binder
|
||||
binder!!.setForegroundActivityIntent(intent)
|
||||
|
||||
player = binder!!.getPlayer()
|
||||
player!!.setView(binding.video)
|
||||
|
||||
player!!.isBuffering
|
||||
.subscribe(::onBufferingChanged, Timber::e)
|
||||
.also(disposable::add)
|
||||
|
||||
player!!.errorResourceId
|
||||
.subscribe(::onVideoError, Timber::e)
|
||||
.also(disposable::add)
|
||||
|
||||
channelDetailListener.onItemSelected(currentChannel!!)
|
||||
|
||||
binder!!.movePlaybackToForeground()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(componentName: ComponentName) {
|
||||
player?.pause()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityChannelDetailBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
playStreamDelayMillis = resources.getInteger(R.integer.activity_channel_detail_play_stream_delay_millis)
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
channelList = (application as ZappApplicationBase).channelRepository.getChannelList()
|
||||
settings = SettingsRepository(this)
|
||||
|
||||
// pager
|
||||
channelDetailAdapter = ChannelDetailAdapter(
|
||||
supportFragmentManager, channelList, channelDetailListener)
|
||||
|
||||
binding.viewpager.adapter = channelDetailAdapter
|
||||
binding.viewpager.addOnPageChangeListener(onPageChangeListener)
|
||||
binding.viewpager.setOnClickListener { contentView.performClick() }
|
||||
binding.viewpager.setOnTouchListener(::onPagerTouch)
|
||||
|
||||
binding.video.setTouchOverlay(binding.viewpager)
|
||||
|
||||
parseIntent(intent)
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
|
||||
// called when coming back from picture in picture mode
|
||||
parseIntent(intent)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
if (isInsideMultiWindow(this)) {
|
||||
resumeActivity()
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (!isInsideMultiWindow(this)) {
|
||||
resumeActivity()
|
||||
}
|
||||
|
||||
requestedOrientation = if (settings.lockVideosInLandcapeFormat) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
if (!isInsideMultiWindow(this)) {
|
||||
pauseActivity()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
pauseActivity()
|
||||
}
|
||||
|
||||
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
|
||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode)
|
||||
if (isInPictureInPictureMode) {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.activity_channel_detail, menu)
|
||||
|
||||
if (!supportsPictureInPictureMode(this)) {
|
||||
menu.removeItem(R.id.menu_pip)
|
||||
}
|
||||
|
||||
return super.onCreateOptionsMenu(menu)
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.menu_share -> {
|
||||
startActivity(Intent.createChooser(currentChannel!!.videoShareIntent, getString(R.string.action_share)))
|
||||
true
|
||||
}
|
||||
R.id.menu_play_in_background -> {
|
||||
binder!!.movePlaybackToBackground()
|
||||
finish()
|
||||
true
|
||||
}
|
||||
R.id.menu_pip -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
enterPictureInPictureMode()
|
||||
}
|
||||
true
|
||||
}
|
||||
android.R.id.home -> {
|
||||
finish()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
||||
var handled = false
|
||||
|
||||
when (keyCode) {
|
||||
KeyEvent.KEYCODE_DPAD_LEFT -> handled = prevChannel()
|
||||
KeyEvent.KEYCODE_DPAD_RIGHT -> handled = nextChannel()
|
||||
}
|
||||
|
||||
return handled || super.onKeyUp(keyCode, event)
|
||||
}
|
||||
|
||||
private fun onVideoError(messageResourceId: Int?) {
|
||||
if (messageResourceId == null || messageResourceId == -1) {
|
||||
return
|
||||
}
|
||||
|
||||
player?.pause()
|
||||
binding.videoProgress.isVisible = false
|
||||
channelDetailAdapter.currentFragment?.onVideoError(getString(messageResourceId))
|
||||
}
|
||||
|
||||
private fun onBufferingChanged(isBuffering: Boolean) {
|
||||
if (isBuffering) {
|
||||
binding.videoProgress.isVisible = true
|
||||
} else if (!player!!.isIdle) {
|
||||
binding.videoProgress.isVisible = false
|
||||
channelDetailAdapter.currentFragment?.onVideoStart()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onErrorViewClicked() {
|
||||
player?.recreate()
|
||||
player?.resume()
|
||||
}
|
||||
|
||||
private fun onPagerTouch(view: View, motionEvent: MotionEvent): Boolean {
|
||||
delayHide()
|
||||
return false
|
||||
}
|
||||
|
||||
private fun parseIntent(intent: Intent) {
|
||||
val extras = intent.extras
|
||||
val channelId = extras!!.getString(EXTRA_CHANNEL_ID)
|
||||
val channelPosition = channelList.indexOf(channelId)
|
||||
|
||||
binding.viewpager.removeOnPageChangeListener(onPageChangeListener)
|
||||
binding.viewpager.currentItem = channelPosition
|
||||
binding.viewpager.addOnPageChangeListener(onPageChangeListener)
|
||||
}
|
||||
|
||||
private fun pauseActivity() {
|
||||
disposable.clear()
|
||||
try {
|
||||
unbindService(backgroundPlayerServiceConnection)
|
||||
} catch (ignored: IllegalArgumentException) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun resumeActivity() {
|
||||
binding.programInfo.resume()
|
||||
bind(this, backgroundPlayerServiceConnection)
|
||||
}
|
||||
|
||||
private fun playDelayed() {
|
||||
if (player != null) {
|
||||
player!!.pause()
|
||||
}
|
||||
|
||||
playHandler.removeCallbacks(playRunnable)
|
||||
playHandler.postDelayed(playRunnable, playStreamDelayMillis.toLong())
|
||||
}
|
||||
|
||||
private fun play() {
|
||||
if (currentChannel == null || binder == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val currentIntent = getStartIntent(this, currentChannel!!.id)
|
||||
binder!!.setForegroundActivityIntent(currentIntent)
|
||||
|
||||
Timber.d("play: %s", currentChannel!!.name)
|
||||
player!!.load(fromChannel(currentChannel!!))
|
||||
player!!.resume()
|
||||
}
|
||||
|
||||
private fun prevChannel(): Boolean {
|
||||
val nextItemIndex = binding.viewpager.currentItem - 1
|
||||
|
||||
return if (nextItemIndex < 0) {
|
||||
false
|
||||
} else {
|
||||
delayHide()
|
||||
binding.viewpager.setCurrentItem(nextItemIndex, true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun nextChannel(): Boolean {
|
||||
val nextItemIndex = binding.viewpager.currentItem + 1
|
||||
|
||||
return if (nextItemIndex == channelDetailAdapter.count) {
|
||||
false
|
||||
} else {
|
||||
delayHide()
|
||||
binding.viewpager.setCurrentItem(nextItemIndex, true)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setColor(color: Int) {
|
||||
binding.videoProgress.indeterminateDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||
binding.toolbar.setBackgroundColor(color)
|
||||
|
||||
window.statusBarColor = darker(color, 0.075f)
|
||||
|
||||
val colorAlpha = darker(withAlpha(color, 150), 0.25f)
|
||||
controlsView.setBackgroundColor(colorAlpha)
|
||||
}
|
||||
}
|
@ -1,69 +0,0 @@
|
||||
package de.christinecoenen.code.zapp.app.livestream.ui.detail;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import de.christinecoenen.code.zapp.models.channels.ChannelModel;
|
||||
import de.christinecoenen.code.zapp.models.channels.IChannelList;
|
||||
|
||||
|
||||
class ChannelDetailAdapter extends FragmentStatePagerAdapter {
|
||||
|
||||
private final IChannelList channelList;
|
||||
private final Listener listener;
|
||||
private StreamPageFragment currentFragment;
|
||||
|
||||
ChannelDetailAdapter(FragmentManager fragmentManager, IChannelList channelList,
|
||||
Listener listener) {
|
||||
super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
|
||||
this.channelList = channelList;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
StreamPageFragment getCurrentFragment() {
|
||||
return currentFragment;
|
||||
}
|
||||
|
||||
ChannelModel getChannel(int index) {
|
||||
return channelList.get(index);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Fragment getItem(int position) {
|
||||
ChannelModel channelModel = channelList.get(position);
|
||||
return StreamPageFragment.newInstance(channelModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return channelList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getPageTitle(int position) {
|
||||
return channelList.get(position).getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
if (currentFragment != object) {
|
||||
if (currentFragment != null) {
|
||||
// tell old fragment it's no longer visible
|
||||
currentFragment.onHide();
|
||||
}
|
||||
|
||||
currentFragment = ((StreamPageFragment) object);
|
||||
listener.onItemSelected(channelList.get(position));
|
||||
}
|
||||
|
||||
super.setPrimaryItem(container, position, object);
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
void onItemSelected(ChannelModel model);
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package de.christinecoenen.code.zapp.app.livestream.ui.detail
|
||||
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.fragment.app.FragmentStatePagerAdapter
|
||||
import de.christinecoenen.code.zapp.models.channels.ChannelModel
|
||||
import de.christinecoenen.code.zapp.models.channels.IChannelList
|
||||
|
||||
internal class ChannelDetailAdapter(
|
||||
fragmentManager: FragmentManager,
|
||||
private val channelList: IChannelList,
|
||||
private val listener: Listener
|
||||
) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
|
||||
|
||||
var currentFragment: StreamPageFragment? = null
|
||||
private set
|
||||
|
||||
fun getChannel(index: Int): ChannelModel {
|
||||
return channelList[index]
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): Fragment {
|
||||
val channelModel = channelList[position]
|
||||
return StreamPageFragment.newInstance(channelModel)
|
||||
}
|
||||
|
||||
override fun getCount(): Int {
|
||||
return channelList.size()
|
||||
}
|
||||
|
||||
override fun getPageTitle(position: Int): CharSequence? {
|
||||
return channelList[position].name
|
||||
}
|
||||
|
||||
override fun setPrimaryItem(container: ViewGroup, position: Int, fragmentArgument: Any) {
|
||||
super.setPrimaryItem(container, position, fragmentArgument)
|
||||
|
||||
if (currentFragment == fragmentArgument) {
|
||||
return
|
||||
}
|
||||
|
||||
// tell old fragment it's no longer visible
|
||||
currentFragment?.onHide()
|
||||
|
||||
currentFragment = fragmentArgument as StreamPageFragment
|
||||
|
||||
listener.onItemSelected(channelList[position])
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onItemSelected(channel: ChannelModel)
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
package de.christinecoenen.code.zapp.app.livestream.ui.detail;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import de.christinecoenen.code.zapp.databinding.FragmentStreamPageBinding;
|
||||
import de.christinecoenen.code.zapp.models.channels.ChannelModel;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class StreamPageFragment extends Fragment {
|
||||
|
||||
private static final String ARGUMENT_CHANNEL_MODEL = "ARGUMENT_CHANNEL_MODEL";
|
||||
|
||||
private View rootView;
|
||||
private TextView errorText;
|
||||
private Listener listener;
|
||||
|
||||
static StreamPageFragment newInstance(ChannelModel channelModel) {
|
||||
StreamPageFragment fragment = new StreamPageFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putSerializable(ARGUMENT_CHANNEL_MODEL, channelModel);
|
||||
fragment.setArguments(args);
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
FragmentStreamPageBinding binding = FragmentStreamPageBinding.inflate(inflater, container, false);
|
||||
|
||||
rootView = binding.getRoot();
|
||||
errorText = binding.textError;
|
||||
|
||||
Bundle args = requireArguments();
|
||||
ChannelModel channel = (ChannelModel) args.getSerializable(ARGUMENT_CHANNEL_MODEL);
|
||||
|
||||
if (channel != null) {
|
||||
ImageView logoView = binding.logo;
|
||||
logoView.setImageResource(channel.getDrawableId());
|
||||
logoView.setContentDescription(channel.getName());
|
||||
errorText.setBackgroundColor(channel.getColor());
|
||||
|
||||
errorText.setOnClickListener(view -> onErrorViewClick());
|
||||
|
||||
if (channel.getSubtitle() != null) {
|
||||
TextView subtitleText = binding.subtitle;
|
||||
subtitleText.setText(channel.getSubtitle());
|
||||
}
|
||||
} else {
|
||||
Timber.w("channel argument is null");
|
||||
}
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
if (context instanceof Listener) {
|
||||
listener = (Listener) context;
|
||||
} else {
|
||||
throw new RuntimeException("Activity must implement StreamPageFragment.Listener.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
listener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
// don't use onPause to support multiwindow feature
|
||||
rootView.setVisibility(View.VISIBLE);
|
||||
errorText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
void onHide() {
|
||||
rootView.setVisibility(View.VISIBLE);
|
||||
errorText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
void onVideoStart() {
|
||||
fadeOutLogo();
|
||||
}
|
||||
|
||||
void onVideoError(String message) {
|
||||
rootView.setVisibility(View.VISIBLE);
|
||||
errorText.setVisibility(View.VISIBLE);
|
||||
errorText.setText(message);
|
||||
}
|
||||
|
||||
private void onErrorViewClick() {
|
||||
listener.onErrorViewClicked();
|
||||
onHide();
|
||||
}
|
||||
|
||||
private void fadeOutLogo() {
|
||||
if (rootView.getVisibility() == View.VISIBLE) {
|
||||
Animation fadeOutAnimation = AnimationUtils.
|
||||
loadAnimation(getContext(), android.R.anim.fade_out);
|
||||
fadeOutAnimation.setAnimationListener(new Animation.AnimationListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animation animation) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animation animation) {
|
||||
rootView.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animation animation) {
|
||||
}
|
||||
});
|
||||
|
||||
rootView.startAnimation(fadeOutAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onErrorViewClicked();
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package de.christinecoenen.code.zapp.app.livestream.ui.detail
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import de.christinecoenen.code.zapp.databinding.FragmentStreamPageBinding
|
||||
import de.christinecoenen.code.zapp.models.channels.ChannelModel
|
||||
|
||||
class StreamPageFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
private const val ARGUMENT_CHANNEL_MODEL = "ARGUMENT_CHANNEL_MODEL"
|
||||
|
||||
fun newInstance(channelModel: ChannelModel?): StreamPageFragment {
|
||||
return StreamPageFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putSerializable(ARGUMENT_CHANNEL_MODEL, channelModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var _binding: FragmentStreamPageBinding? = null
|
||||
private val binding: FragmentStreamPageBinding get() = _binding!!
|
||||
|
||||
private var listener: Listener? = null
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = FragmentStreamPageBinding.inflate(inflater, container, false)
|
||||
|
||||
val channel = requireArguments().getSerializable(ARGUMENT_CHANNEL_MODEL) as ChannelModel?
|
||||
?: throw IllegalArgumentException("channel argument is null")
|
||||
|
||||
binding.logo.setImageResource(channel.drawableId)
|
||||
binding.logo.contentDescription = channel.name
|
||||
|
||||
binding.textError.setBackgroundColor(channel.color)
|
||||
binding.textError.setOnClickListener { onErrorViewClick() }
|
||||
|
||||
if (channel.subtitle != null) {
|
||||
binding.subtitle.text = channel.subtitle
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
listener = if (context is Listener) {
|
||||
context
|
||||
} else {
|
||||
throw RuntimeException("Activity must implement StreamPageFragment.Listener.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
super.onDetach()
|
||||
listener = null
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
|
||||
// don't use onPause to support multiwindow feature
|
||||
binding.root.isVisible = true
|
||||
binding.textError.isVisible = false
|
||||
}
|
||||
|
||||
fun onHide() {
|
||||
binding.root.isVisible = true
|
||||
binding.textError.isVisible = false
|
||||
}
|
||||
|
||||
fun onVideoStart() {
|
||||
fadeOutLogo()
|
||||
}
|
||||
|
||||
fun onVideoError(message: String?) {
|
||||
binding.root.isVisible = true
|
||||
binding.textError.isVisible = true
|
||||
binding.textError.text = message
|
||||
}
|
||||
|
||||
private fun onErrorViewClick() {
|
||||
listener?.onErrorViewClicked()
|
||||
onHide()
|
||||
}
|
||||
|
||||
private fun fadeOutLogo() {
|
||||
if (!binding.root.isVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
val fadeOutAnimation = AnimationUtils.loadAnimation(context, android.R.anim.fade_out)
|
||||
|
||||
fadeOutAnimation.setAnimationListener(object : Animation.AnimationListener {
|
||||
override fun onAnimationRepeat(animation: Animation) {}
|
||||
override fun onAnimationStart(animation: Animation) {}
|
||||
override fun onAnimationEnd(animation: Animation) {
|
||||
binding.root.isVisible = false
|
||||
}
|
||||
})
|
||||
|
||||
binding.root.startAnimation(fadeOutAnimation)
|
||||
}
|
||||
|
||||
interface Listener {
|
||||
fun onErrorViewClicked()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user