@ -58,7 +58,6 @@ import com.keylesspalace.tusky.viewdata.NotificationViewData;
import com.keylesspalace.tusky.viewdata.StatusViewData ;
import java.io.IOException ;
import java.util.Collections ;
import java.util.Iterator ;
import java.util.List ;
import java.util.Objects ;
@ -70,11 +69,16 @@ import androidx.annotation.Nullable;
import androidx.arch.core.util.Function ;
import androidx.core.util.Pair ;
import androidx.lifecycle.Lifecycle ;
import androidx.recyclerview.widget.AsyncDifferConfig ;
import androidx.recyclerview.widget.AsyncListDiffer ;
import androidx.recyclerview.widget.DiffUtil ;
import androidx.recyclerview.widget.DividerItemDecoration ;
import androidx.recyclerview.widget.LinearLayoutManager ;
import androidx.recyclerview.widget.ListUpdateCallback ;
import androidx.recyclerview.widget.RecyclerView ;
import androidx.recyclerview.widget.SimpleItemAnimator ;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout ;
import at.connyduck.sparkbutton.helpers.Utils ;
import io.reactivex.android.schedulers.AndroidSchedulers ;
import kotlin.Unit ;
import kotlin.collections.CollectionsKt ;
@ -94,6 +98,7 @@ public class NotificationsFragment extends SFragment implements
private static final String TAG = "NotificationF" ; // logging tag
private static final int LOAD_AT_ONCE = 30 ;
private int maxPlaceholderId = 0 ;
private enum FetchEnd {
TOP ,
@ -106,13 +111,14 @@ public class NotificationsFragment extends SFragment implements
* and reuse in different places as needed .
* /
private static final class Placeholder {
private static final Placeholder INSTANCE = new Placeholder ( ) ;
final long id ;
public static Placeholder getInstance ( ) {
return INSTANCE ;
public static Placeholder getInstance ( long id ) {
return new Placeholder ( id ) ;
}
private Placeholder ( ) {
private Placeholder ( long id ) {
this . id = id ;
}
}
@ -155,7 +161,7 @@ public class NotificationsFragment extends SFragment implements
alwaysShowSensitiveMedia
) ;
} else {
return new NotificationViewData . Placeholder ( false ) ;
return new NotificationViewData . Placeholder ( input . asLeft ( ) . id , false ) ;
}
}
} ) ;
@ -200,7 +206,7 @@ public class NotificationsFragment extends SFragment implements
recyclerView . addItemDecoration ( new DividerItemDecoration ( context , DividerItemDecoration . VERTICAL ) ) ;
adapter = new NotificationsAdapter ( this , this ) ;
adapter = new NotificationsAdapter ( dataSource , this , this ) ;
SharedPreferences preferences = PreferenceManager . getDefaultSharedPreferences ( getActivity ( ) ) ;
alwaysShowSensitiveMedia = accountManager . getActiveAccount ( ) . getAlwaysShowSensitiveMedia ( ) ;
boolean mediaPreviewEnabled = accountManager . getActiveAccount ( ) . getMediaPreviewEnabled ( ) ;
@ -380,11 +386,9 @@ public class NotificationsFragment extends SFragment implements
viewDataBuilder . createStatusViewData ( ) , viewdata . isExpanded ( ) ) ;
notifications . setPairedItem ( position , newViewData ) ;
adapter . updateItemWithNotify ( position , newViewData , true ) ;
updateAdapter ( ) ;
}
@Override
public void onFavourite ( final boolean favourite , final int position ) {
final Notification notification = notifications . get ( position ) . asRight ( ) ;
@ -417,8 +421,7 @@ public class NotificationsFragment extends SFragment implements
viewDataBuilder . createStatusViewData ( ) , viewdata . isExpanded ( ) ) ;
notifications . setPairedItem ( position , newViewData ) ;
adapter . updateItemWithNotify ( position , newViewData , true ) ;
updateAdapter ( ) ;
}
@Override
@ -457,7 +460,7 @@ public class NotificationsFragment extends SFragment implements
NotificationViewData notificationViewData = new NotificationViewData . Concrete ( old . getType ( ) ,
old . getId ( ) , old . getAccount ( ) , statusViewData , expanded ) ;
notifications . setPairedItem ( position , notificationViewData ) ;
adapter . updateItemWithNotify ( position , notificationViewData , false ) ;
updateAdapter ( ) ;
}
@Override
@ -471,7 +474,7 @@ public class NotificationsFragment extends SFragment implements
NotificationViewData notificationViewData = new NotificationViewData . Concrete ( old . getType ( ) ,
old . getId ( ) , old . getAccount ( ) , statusViewData , old . isExpanded ( ) ) ;
notifications . setPairedItem ( position , notificationViewData ) ;
adapter . updateItemWithNotify ( position , notificationViewData , false ) ;
updateAdapter ( ) ;
}
@Override
@ -485,10 +488,11 @@ public class NotificationsFragment extends SFragment implements
return ;
}
sendFetchNotificationsRequest ( previous . getId ( ) , next . getId ( ) , FetchEnd . MIDDLE , position ) ;
Placeholder placeholder = notifications . get ( position ) . asLeft ( ) ;
NotificationViewData notificationViewData =
new NotificationViewData . Placeholder ( true ) ;
new NotificationViewData . Placeholder ( placeholder . id , true ) ;
notifications . setPairedItem ( position , notificationViewData ) ;
adapter . updateItemWithNotify ( position , notificationViewData , false ) ;
updateAdapter ( ) ;
} else {
Log . d ( TAG , "error loading more" ) ;
}
@ -526,7 +530,7 @@ public class NotificationsFragment extends SFragment implements
concreteNotification . isExpanded ( )
) ;
notifications . setPairedItem ( position , updatedNotification ) ;
adapter . updateItemWithNotify ( position , updatedNotification , false ) ;
updateAdapter ( ) ;
// Since we cannot notify to the RecyclerView right away because it may be scrolling
// we run this when the RecyclerView is done doing measurements and other calculations.
@ -582,7 +586,7 @@ public class NotificationsFragment extends SFragment implements
@Override
public void removeItem ( int position ) {
notifications . remove ( position ) ;
adapter . update ( notifications . getPairedCopy ( ) ) ;
updateAdapter ( ) ;
}
private void removeAllByAccountId ( String accountId ) {
@ -595,7 +599,7 @@ public class NotificationsFragment extends SFragment implements
iterator . remove ( ) ;
}
}
adapter . update ( notifications . getPairedCopy ( ) ) ;
updateAdapter ( ) ;
}
private void onLoadMore ( ) {
@ -610,16 +614,24 @@ public class NotificationsFragment extends SFragment implements
if ( notifications . size ( ) > 0 ) {
Either < Placeholder , Notification > last = notifications . get ( notifications . size ( ) - 1 ) ;
if ( last . isRight ( ) ) {
notifications . add ( new Either . Left ( Placeholder . getInstance ( ) ) ) ;
NotificationViewData viewData = new NotificationViewData . Placeholder ( true ) ;
final Placeholder placeholder = newPlaceholder ( ) ;
notifications . add ( new Either . Left < > ( placeholder ) ) ;
NotificationViewData viewData =
new NotificationViewData . Placeholder ( placeholder . id , true ) ;
notifications . setPairedItem ( notifications . size ( ) - 1 , viewData ) ;
recyclerView . post ( ( ) - > adapter . addItems ( Collections . singletonList ( viewData ) ) ) ;
updateAdapter ( ) ;
}
}
sendFetchNotificationsRequest ( bottomId , null , FetchEnd . BOTTOM , - 1 ) ;
}
private Placeholder newPlaceholder ( ) {
Placeholder placeholder = Placeholder . getInstance ( maxPlaceholderId ) ;
maxPlaceholderId - - ;
return placeholder ;
}
private void jumpToTop ( ) {
layoutManager . scrollToPosition ( 0 ) ;
scrollListener . reset ( ) ;
@ -669,11 +681,6 @@ public class NotificationsFragment extends SFragment implements
List < HttpHeaderLink > links = HttpHeaderLink . parse ( linkHeader ) ;
switch ( fetchEnd ) {
case TOP : {
HttpHeaderLink previous = HttpHeaderLink . findByRelationType ( links , "prev" ) ;
String uptoId = null ;
if ( previous ! = null ) {
uptoId = previous . uri . getQueryParameter ( "since_id" ) ;
}
update ( notifications , null ) ;
break ;
}
@ -691,20 +698,12 @@ public class NotificationsFragment extends SFragment implements
if ( ! this . notifications . isEmpty ( )
& & ! this . notifications . get ( this . notifications . size ( ) - 1 ) . isRight ( ) ) {
this . notifications . remove ( this . notifications . size ( ) - 1 ) ;
adapter . removeItemAndNotify ( this . notifications . size ( ) ) ;
updateAdapter ( ) ;
}
if ( adapter . getItemCount ( ) > 0 ) {
addItems ( notifications , fromId ) ;
} else {
/ * If this is the first fetch , also save the id from the "previous" link and
* treat this operation as a refresh so the scroll position doesn ' t get pushed
* down to the end . * /
HttpHeaderLink previous = HttpHeaderLink . findByRelationType ( links , "prev" ) ;
String uptoId = null ;
if ( previous ! = null ) {
uptoId = previous . uri . getQueryParameter ( "since_id" ) ;
}
update ( notifications , fromId ) ;
}
@ -733,10 +732,11 @@ public class NotificationsFragment extends SFragment implements
private void onFetchNotificationsFailure ( Exception exception , FetchEnd fetchEnd , int position ) {
swipeRefreshLayout . setRefreshing ( false ) ;
if ( fetchEnd = = FetchEnd . MIDDLE & & ! notifications . get ( position ) . isRight ( ) ) {
Placeholder placeholder = notifications . get ( position ) . asLeft ( ) ;
NotificationViewData placeholderVD =
new NotificationViewData . Placeholder ( false ) ;
new NotificationViewData . Placeholder ( placeholder . id , false ) ;
notifications . setPairedItem ( position , placeholderVD ) ;
adapter . updateItemWithNotify ( position , placeholderVD , true ) ;
updateAdapter ( ) ;
} else if ( this . notifications . isEmpty ( ) ) {
this . statusView . setVisibility ( View . VISIBLE ) ;
swipeRefreshLayout . setEnabled ( false ) ;
@ -798,14 +798,14 @@ public class NotificationsFragment extends SFragment implements
int newIndex = liftedNew . indexOf ( notifications . get ( 0 ) ) ;
if ( newIndex = = - 1 ) {
if ( index = = - 1 & & liftedNew . size ( ) > = LOAD_AT_ONCE ) {
liftedNew . add ( new Either . Left ( Placeholder . getInstance ( ) ) ) ;
liftedNew . add ( new Either . Left < > ( newPlaceholder ( ) ) ) ;
}
notifications . addAll ( 0 , liftedNew ) ;
} else {
notifications . addAll ( 0 , liftedNew . subList ( 0 , newIndex ) ) ;
}
}
adapter . update ( notifications . getPairedCopy ( ) ) ;
updateAdapter ( ) ;
}
private void addItems ( List < Notification > newNotifications , @Nullable String fromId ) {
@ -818,10 +818,7 @@ public class NotificationsFragment extends SFragment implements
Either < Placeholder , Notification > last = notifications . get ( end - 1 ) ;
if ( last ! = null & & liftedNew . indexOf ( last ) = = - 1 ) {
notifications . addAll ( liftedNew ) ;
List < NotificationViewData > newViewDatas = notifications . getPairedCopy ( )
. subList ( notifications . size ( ) - newNotifications . size ( ) ,
notifications . size ( ) ) ;
adapter . addItems ( newViewDatas ) ;
updateAdapter ( ) ;
}
}
@ -830,7 +827,7 @@ public class NotificationsFragment extends SFragment implements
notifications . remove ( pos ) ;
if ( ListUtils . isEmpty ( newNotifications ) ) {
adapter . update ( notifications . getPairedCopy ( ) ) ;
updateAdapter ( ) ;
return ;
}
@ -840,11 +837,11 @@ public class NotificationsFragment extends SFragment implements
// If we fetched at least as much it means that there are more posts to load and we should
// insert new placeholder
if ( newNotifications . size ( ) > = LOAD_AT_ONCE ) {
liftedNew . add ( new Either . Left ( Placeholder . getInstance ( ) ) ) ;
liftedNew . add ( new Either . Left < > ( newPlaceholder ( ) ) ) ;
}
notifications . addAll ( pos , liftedNew ) ;
adapter . update ( notifications . getPairedCopy ( ) ) ;
updateAdapter ( ) ;
}
private final Function < Notification , Either < Placeholder , Notification > > notificationLifter =
@ -855,8 +852,8 @@ public class NotificationsFragment extends SFragment implements
}
private void fullyRefresh ( ) {
adapter . clear ( ) ;
notifications . clear ( ) ;
updateAdapter ( ) ;
sendFetchNotificationsRequest ( null , null , FetchEnd . TOP , - 1 ) ;
}
@ -875,4 +872,67 @@ public class NotificationsFragment extends SFragment implements
}
return null ;
}
private void updateAdapter ( ) {
differ . submitList ( notifications . getPairedCopy ( ) ) ;
}
private final ListUpdateCallback listUpdateCallback = new ListUpdateCallback ( ) {
@Override
public void onInserted ( int position , int count ) {
if ( isAdded ( ) ) {
adapter . notifyItemRangeInserted ( position , count ) ;
Context context = getContext ( ) ;
if ( position = = 0 & & context ! = null ) {
recyclerView . scrollBy ( 0 , Utils . dpToPx ( context , - 30 ) ) ;
}
}
}
@Override
public void onRemoved ( int position , int count ) {
adapter . notifyItemRangeRemoved ( position , count ) ;
}
@Override
public void onMoved ( int fromPosition , int toPosition ) {
adapter . notifyItemMoved ( fromPosition , toPosition ) ;
}
@Override
public void onChanged ( int position , int count , Object payload ) {
adapter . notifyItemRangeChanged ( position , count , payload ) ;
}
} ;
private final AsyncListDiffer < NotificationViewData >
differ = new AsyncListDiffer < > ( listUpdateCallback ,
new AsyncDifferConfig . Builder < > ( diffCallback ) . build ( ) ) ;
private final NotificationsAdapter . AdapterDataSource < NotificationViewData > dataSource =
new NotificationsAdapter . AdapterDataSource < NotificationViewData > ( ) {
@Override
public int getItemCount ( ) {
return differ . getCurrentList ( ) . size ( ) ;
}
@Override
public NotificationViewData getItemAt ( int pos ) {
return differ . getCurrentList ( ) . get ( pos ) ;
}
} ;
private static final DiffUtil . ItemCallback < NotificationViewData > diffCallback
= new DiffUtil . ItemCallback < NotificationViewData > ( ) {
@Override
public boolean areItemsTheSame ( NotificationViewData oldItem , NotificationViewData newItem ) {
return oldItem . getViewDataId ( ) = = newItem . getViewDataId ( ) ;
}
@Override
public boolean areContentsTheSame ( NotificationViewData oldItem , NotificationViewData newItem ) {
return oldItem . deepEquals ( newItem ) ;
}
} ;
}