2017-09-03 08:04:18 +02:00
package org.schabi.newpipe.fragments.list.channel ;
import android.content.Context ;
import android.content.Intent ;
import android.net.Uri ;
import android.os.Bundle ;
import android.support.annotation.NonNull ;
import android.support.annotation.Nullable ;
import android.support.v4.content.ContextCompat ;
2017-09-26 18:16:39 +02:00
import android.support.v7.app.ActionBar ;
2017-09-03 08:04:18 +02:00
import android.text.TextUtils ;
import android.util.Log ;
import android.view.LayoutInflater ;
import android.view.Menu ;
import android.view.MenuInflater ;
import android.view.MenuItem ;
import android.view.View ;
import android.view.ViewGroup ;
import android.widget.Button ;
import android.widget.ImageView ;
import android.widget.TextView ;
import com.jakewharton.rxbinding2.view.RxView ;
import org.schabi.newpipe.R ;
import org.schabi.newpipe.database.subscription.SubscriptionEntity ;
import org.schabi.newpipe.extractor.ListExtractor ;
import org.schabi.newpipe.extractor.NewPipe ;
import org.schabi.newpipe.extractor.channel.ChannelInfo ;
import org.schabi.newpipe.extractor.exceptions.ExtractionException ;
import org.schabi.newpipe.fragments.list.BaseListInfoFragment ;
import org.schabi.newpipe.fragments.subscription.SubscriptionService ;
import org.schabi.newpipe.report.UserAction ;
import org.schabi.newpipe.util.AnimationUtils ;
import org.schabi.newpipe.util.ExtractorHelper ;
import org.schabi.newpipe.util.Localization ;
import java.util.List ;
import java.util.concurrent.TimeUnit ;
import io.reactivex.Observable ;
import io.reactivex.Single ;
import io.reactivex.android.schedulers.AndroidSchedulers ;
import io.reactivex.disposables.CompositeDisposable ;
import io.reactivex.disposables.Disposable ;
import io.reactivex.functions.Action ;
import io.reactivex.functions.Consumer ;
import io.reactivex.functions.Function ;
import io.reactivex.schedulers.Schedulers ;
import static org.schabi.newpipe.util.AnimationUtils.animateBackgroundColor ;
import static org.schabi.newpipe.util.AnimationUtils.animateTextColor ;
import static org.schabi.newpipe.util.AnimationUtils.animateView ;
public class ChannelFragment extends BaseListInfoFragment < ChannelInfo > {
private CompositeDisposable disposables = new CompositeDisposable ( ) ;
private Disposable subscribeButtonMonitor ;
private SubscriptionService subscriptionService ;
/ * //////////////////////////////////////////////////////////////////////////
// Views
//////////////////////////////////////////////////////////////////////////*/
private View headerRootLayout ;
private ImageView headerChannelBanner ;
private ImageView headerAvatarView ;
private TextView headerTitleView ;
private TextView headerSubscribersTextView ;
private Button headerSubscribeButton ;
private MenuItem menuRssButton ;
public static ChannelFragment getInstance ( int serviceId , String url , String name ) {
ChannelFragment instance = new ChannelFragment ( ) ;
instance . setInitialData ( serviceId , url , name ) ;
return instance ;
}
/ * //////////////////////////////////////////////////////////////////////////
// LifeCycle
//////////////////////////////////////////////////////////////////////////*/
2017-10-26 20:45:16 +02:00
@Override
public void setUserVisibleHint ( boolean isVisibleToUser ) {
super . setUserVisibleHint ( isVisibleToUser ) ;
if ( activity ! = null
& & useAsFrontPage
& & isVisibleToUser ) {
try {
activity . getSupportActionBar ( ) . setTitle ( currentInfo . name ) ;
} catch ( Exception e ) {
onError ( e ) ;
}
}
}
2017-09-03 08:04:18 +02:00
@Override
public void onAttach ( Context context ) {
super . onAttach ( context ) ;
subscriptionService = SubscriptionService . getInstance ( ) ;
}
@Override
public View onCreateView ( LayoutInflater inflater , @Nullable ViewGroup container , @Nullable Bundle savedInstanceState ) {
2017-11-01 16:26:44 +01:00
return inflater . inflate ( R . layout . fragment_channel , container , false ) ;
2017-09-03 08:04:18 +02:00
}
@Override
public void onDestroy ( ) {
super . onDestroy ( ) ;
if ( disposables ! = null ) disposables . clear ( ) ;
if ( subscribeButtonMonitor ! = null ) subscribeButtonMonitor . dispose ( ) ;
}
/ * //////////////////////////////////////////////////////////////////////////
// Init
//////////////////////////////////////////////////////////////////////////*/
protected View getListHeader ( ) {
headerRootLayout = activity . getLayoutInflater ( ) . inflate ( R . layout . channel_header , itemsList , false ) ;
headerChannelBanner = headerRootLayout . findViewById ( R . id . channel_banner_image ) ;
headerAvatarView = headerRootLayout . findViewById ( R . id . channel_avatar_view ) ;
headerTitleView = headerRootLayout . findViewById ( R . id . channel_title_view ) ;
headerSubscribersTextView = headerRootLayout . findViewById ( R . id . channel_subscriber_view ) ;
headerSubscribeButton = headerRootLayout . findViewById ( R . id . channel_subscribe_button ) ;
2017-09-28 16:15:09 +02:00
2017-09-03 08:04:18 +02:00
return headerRootLayout ;
}
/ * //////////////////////////////////////////////////////////////////////////
// Menu
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onCreateOptionsMenu ( Menu menu , MenuInflater inflater ) {
super . onCreateOptionsMenu ( menu , inflater ) ;
2017-09-26 18:16:39 +02:00
ActionBar supportActionBar = activity . getSupportActionBar ( ) ;
if ( useAsFrontPage ) {
supportActionBar . setDisplayHomeAsUpEnabled ( false ) ;
} else {
inflater . inflate ( R . menu . menu_channel , menu ) ;
if ( DEBUG ) Log . d ( TAG , " onCreateOptionsMenu() called with: menu = [ " + menu + " ], inflater = [ " + inflater + " ] " ) ;
menuRssButton = menu . findItem ( R . id . menu_item_rss ) ;
if ( currentInfo ! = null ) {
menuRssButton . setVisible ( ! TextUtils . isEmpty ( currentInfo . feed_url ) ) ;
}
2017-09-03 08:04:18 +02:00
}
}
2017-11-01 16:26:44 +01:00
private void openRssFeed ( ) {
final ChannelInfo info = currentInfo ;
if ( info ! = null ) {
Intent intent = new Intent ( Intent . ACTION_VIEW , Uri . parse ( info . feed_url ) ) ;
startActivity ( intent ) ;
}
}
private void openChannelUriInBrowser ( ) {
Intent intent = new Intent ( Intent . ACTION_VIEW , Uri . parse ( url ) ) ;
startActivity ( intent ) ;
}
private void shareChannelUri ( ) {
Intent intent = new Intent ( Intent . ACTION_SEND ) ;
intent . setType ( " text/plain " ) ;
intent . putExtra ( Intent . EXTRA_TEXT , url ) ;
startActivity ( Intent . createChooser ( intent , getString ( R . string . share_dialog_title ) ) ) ;
}
2017-09-03 08:04:18 +02:00
@Override
public boolean onOptionsItemSelected ( MenuItem item ) {
switch ( item . getItemId ( ) ) {
2017-11-01 16:26:44 +01:00
case R . id . menu_item_rss :
openRssFeed ( ) ;
break ;
case R . id . menu_item_openInBrowser :
openChannelUriInBrowser ( ) ;
break ;
2017-10-31 09:25:27 +01:00
case R . id . menu_item_share : {
2017-11-01 16:26:44 +01:00
shareChannelUri ( ) ;
break ;
2017-10-31 09:25:27 +01:00
}
2017-09-03 08:04:18 +02:00
default :
return super . onOptionsItemSelected ( item ) ;
}
2017-11-01 16:26:44 +01:00
return true ;
2017-09-03 08:04:18 +02:00
}
/ * //////////////////////////////////////////////////////////////////////////
// Channel Subscription
//////////////////////////////////////////////////////////////////////////*/
private static final int BUTTON_DEBOUNCE_INTERVAL = 100 ;
private void monitorSubscription ( final ChannelInfo info ) {
final Consumer < Throwable > onError = new Consumer < Throwable > ( ) {
@Override
public void accept ( Throwable throwable ) throws Exception {
animateView ( headerSubscribeButton , false , 100 ) ;
showSnackBarError ( throwable , UserAction . SUBSCRIPTION , NewPipe . getNameOfService ( currentInfo . service_id ) , " Get subscription status " , 0 ) ;
}
} ;
final Observable < List < SubscriptionEntity > > observable = subscriptionService . subscriptionTable ( )
. getSubscription ( info . service_id , info . url )
. toObservable ( ) ;
disposables . add ( observable
. observeOn ( AndroidSchedulers . mainThread ( ) )
. subscribe ( getSubscribeUpdateMonitor ( info ) , onError ) ) ;
disposables . add ( observable
// Some updates are very rapid (when calling the updateSubscription(info), for example)
// so only update the UI for the latest emission ("sync" the subscribe button's state)
. debounce ( 100 , TimeUnit . MILLISECONDS )
. observeOn ( AndroidSchedulers . mainThread ( ) )
. subscribe ( new Consumer < List < SubscriptionEntity > > ( ) {
@Override
public void accept ( List < SubscriptionEntity > subscriptionEntities ) throws Exception {
updateSubscribeButton ( ! subscriptionEntities . isEmpty ( ) ) ;
}
} , onError ) ) ;
}
private Function < Object , Object > mapOnSubscribe ( final SubscriptionEntity subscription ) {
return new Function < Object , Object > ( ) {
@Override
public Object apply ( @NonNull Object o ) throws Exception {
subscriptionService . subscriptionTable ( ) . insert ( subscription ) ;
return o ;
}
} ;
}
private Function < Object , Object > mapOnUnsubscribe ( final SubscriptionEntity subscription ) {
return new Function < Object , Object > ( ) {
@Override
public Object apply ( @NonNull Object o ) throws Exception {
subscriptionService . subscriptionTable ( ) . delete ( subscription ) ;
return o ;
}
} ;
}
private void updateSubscription ( final ChannelInfo info ) {
if ( DEBUG ) Log . d ( TAG , " updateSubscription() called with: info = [ " + info + " ] " ) ;
final Action onComplete = new Action ( ) {
@Override
public void run ( ) throws Exception {
if ( DEBUG ) Log . d ( TAG , " Updated subscription: " + info . url ) ;
}
} ;
final Consumer < Throwable > onError = new Consumer < Throwable > ( ) {
@Override
public void accept ( @NonNull Throwable throwable ) throws Exception {
onUnrecoverableError ( throwable , UserAction . SUBSCRIPTION , NewPipe . getNameOfService ( info . service_id ) , " Updating Subscription for " + info . url , R . string . subscription_update_failed ) ;
}
} ;
disposables . add ( subscriptionService . updateChannelInfo ( info )
. subscribeOn ( Schedulers . io ( ) )
. observeOn ( AndroidSchedulers . mainThread ( ) )
. subscribe ( onComplete , onError ) ) ;
}
private Disposable monitorSubscribeButton ( final Button subscribeButton , final Function < Object , Object > action ) {
final Consumer < Object > onNext = new Consumer < Object > ( ) {
@Override
public void accept ( @NonNull Object o ) throws Exception {
if ( DEBUG ) Log . d ( TAG , " Changed subscription status to this channel! " ) ;
}
} ;
final Consumer < Throwable > onError = new Consumer < Throwable > ( ) {
@Override
public void accept ( @NonNull Throwable throwable ) throws Exception {
onUnrecoverableError ( throwable , UserAction . SUBSCRIPTION , NewPipe . getNameOfService ( currentInfo . service_id ) , " Subscription Change " , R . string . subscription_change_failed ) ;
}
} ;
/* Emit clicks from main thread unto io thread */
return RxView . clicks ( subscribeButton )
. subscribeOn ( AndroidSchedulers . mainThread ( ) )
. observeOn ( Schedulers . io ( ) )
. debounce ( BUTTON_DEBOUNCE_INTERVAL , TimeUnit . MILLISECONDS ) // Ignore rapid clicks
. map ( action )
. subscribe ( onNext , onError ) ;
}
private Consumer < List < SubscriptionEntity > > getSubscribeUpdateMonitor ( final ChannelInfo info ) {
return new Consumer < List < SubscriptionEntity > > ( ) {
@Override
public void accept ( List < SubscriptionEntity > subscriptionEntities ) throws Exception {
if ( DEBUG )
Log . d ( TAG , " subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [ " + subscriptionEntities + " ] " ) ;
if ( subscribeButtonMonitor ! = null ) subscribeButtonMonitor . dispose ( ) ;
if ( subscriptionEntities . isEmpty ( ) ) {
if ( DEBUG ) Log . d ( TAG , " No subscription to this channel! " ) ;
SubscriptionEntity channel = new SubscriptionEntity ( ) ;
channel . setServiceId ( info . service_id ) ;
channel . setUrl ( info . url ) ;
channel . setData ( info . name , info . avatar_url , info . description , info . subscriber_count ) ;
subscribeButtonMonitor = monitorSubscribeButton ( headerSubscribeButton , mapOnSubscribe ( channel ) ) ;
} else {
if ( DEBUG ) Log . d ( TAG , " Found subscription to this channel! " ) ;
final SubscriptionEntity subscription = subscriptionEntities . get ( 0 ) ;
subscribeButtonMonitor = monitorSubscribeButton ( headerSubscribeButton , mapOnUnsubscribe ( subscription ) ) ;
}
}
} ;
}
private void updateSubscribeButton ( boolean isSubscribed ) {
if ( DEBUG ) Log . d ( TAG , " updateSubscribeButton() called with: isSubscribed = [ " + isSubscribed + " ] " ) ;
boolean isButtonVisible = headerSubscribeButton . getVisibility ( ) = = View . VISIBLE ;
int backgroundDuration = isButtonVisible ? 300 : 0 ;
int textDuration = isButtonVisible ? 200 : 0 ;
int subscribeBackground = ContextCompat . getColor ( activity , R . color . subscribe_background_color ) ;
int subscribeText = ContextCompat . getColor ( activity , R . color . subscribe_text_color ) ;
int subscribedBackground = ContextCompat . getColor ( activity , R . color . subscribed_background_color ) ;
int subscribedText = ContextCompat . getColor ( activity , R . color . subscribed_text_color ) ;
if ( ! isSubscribed ) {
headerSubscribeButton . setText ( R . string . subscribe_button_title ) ;
animateBackgroundColor ( headerSubscribeButton , backgroundDuration , subscribedBackground , subscribeBackground ) ;
animateTextColor ( headerSubscribeButton , textDuration , subscribedText , subscribeText ) ;
} else {
headerSubscribeButton . setText ( R . string . subscribed_button_title ) ;
animateBackgroundColor ( headerSubscribeButton , backgroundDuration , subscribeBackground , subscribedBackground ) ;
animateTextColor ( headerSubscribeButton , textDuration , subscribeText , subscribedText ) ;
}
animateView ( headerSubscribeButton , AnimationUtils . Type . LIGHT_SCALE_AND_ALPHA , true , 100 ) ;
}
/ * //////////////////////////////////////////////////////////////////////////
// Load and handle
//////////////////////////////////////////////////////////////////////////*/
@Override
protected Single < ListExtractor . NextItemsResult > loadMoreItemsLogic ( ) {
2017-09-11 16:21:01 +02:00
return ExtractorHelper . getMoreChannelItems ( serviceId , url , currentNextItemsUrl ) ;
2017-09-03 08:04:18 +02:00
}
@Override
protected Single < ChannelInfo > loadResult ( boolean forceLoad ) {
return ExtractorHelper . getChannelInfo ( serviceId , url , forceLoad ) ;
}
/ * //////////////////////////////////////////////////////////////////////////
// Contract
//////////////////////////////////////////////////////////////////////////*/
@Override
public void showLoading ( ) {
super . showLoading ( ) ;
imageLoader . cancelDisplayTask ( headerChannelBanner ) ;
imageLoader . cancelDisplayTask ( headerAvatarView ) ;
animateView ( headerSubscribeButton , false , 100 ) ;
}
@Override
public void handleResult ( @NonNull ChannelInfo result ) {
super . handleResult ( result ) ;
headerRootLayout . setVisibility ( View . VISIBLE ) ;
imageLoader . displayImage ( result . banner_url , headerChannelBanner , DISPLAY_BANNER_OPTIONS ) ;
imageLoader . displayImage ( result . avatar_url , headerAvatarView , DISPLAY_AVATAR_OPTIONS ) ;
if ( result . subscriber_count ! = - 1 ) {
headerSubscribersTextView . setText ( Localization . localizeSubscribersCount ( activity , result . subscriber_count ) ) ;
headerSubscribersTextView . setVisibility ( View . VISIBLE ) ;
} else headerSubscribersTextView . setVisibility ( View . GONE ) ;
if ( menuRssButton ! = null ) menuRssButton . setVisible ( ! TextUtils . isEmpty ( result . feed_url ) ) ;
if ( ! result . errors . isEmpty ( ) ) {
showSnackBarError ( result . errors , UserAction . REQUESTED_CHANNEL , NewPipe . getNameOfService ( result . service_id ) , result . url , 0 ) ;
}
if ( disposables ! = null ) disposables . clear ( ) ;
if ( subscribeButtonMonitor ! = null ) subscribeButtonMonitor . dispose ( ) ;
updateSubscription ( result ) ;
monitorSubscription ( result ) ;
}
@Override
public void handleNextItems ( ListExtractor . NextItemsResult result ) {
super . handleNextItems ( result ) ;
if ( ! result . errors . isEmpty ( ) ) {
showSnackBarError ( result . errors , UserAction . REQUESTED_CHANNEL , NewPipe . getNameOfService ( serviceId ) ,
" Get next page of: " + url , R . string . general_error ) ;
}
}
/ * //////////////////////////////////////////////////////////////////////////
// OnError
//////////////////////////////////////////////////////////////////////////*/
@Override
protected boolean onError ( Throwable exception ) {
if ( super . onError ( exception ) ) return true ;
int errorId = exception instanceof ExtractionException ? R . string . parsing_error : R . string . general_error ;
onUnrecoverableError ( exception , UserAction . REQUESTED_CHANNEL , NewPipe . getNameOfService ( serviceId ) , url , errorId ) ;
return true ;
}
/ * //////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
@Override
public void setTitle ( String title ) {
super . setTitle ( title ) ;
headerTitleView . setText ( title ) ;
}
}