0
0
mirror of https://github.com/mediathekview/zapp.git synced 2024-09-20 04:12:14 +02:00

Convert StreamPageFragment to kotlin

This commit is contained in:
Christine Emrich 2021-01-02 16:00:39 +01:00
parent 1b7d1f4ac1
commit 94f86af588
7 changed files with 532 additions and 595 deletions

View File

@ -2,9 +2,8 @@
<project version="4"> <project version="4">
<component name="ProjectModuleManager"> <component name="ProjectModuleManager">
<modules> <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$/.idea/modules/2016-09_Zapp.iml" filepath="$PROJECT_DIR$/.idea/modules/2016-09_Zapp.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" group="2016-09_Zapp/app" /> <module fileurl="file://$PROJECT_DIR$/.idea/modules/app/2016-09_Zapp.app.iml" filepath="$PROJECT_DIR$/.idea/modules/app/2016-09_Zapp.app.iml" />
<module fileurl="file://$PROJECT_DIR$/zapp.iml" filepath="$PROJECT_DIR$/zapp.iml" />
</modules> </modules>
</component> </component>
</project> </project>

View File

@ -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);
}
}

View File

@ -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)
}
}

View File

@ -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);
}
}

View File

@ -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)
}
}

View File

@ -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();
}
}

View File

@ -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()
}
}