mirror of
				https://codeberg.org/tom79/Fedilab.git
				synced 2025-10-20 11:20:16 +03:00 
			
		
		
		
	Nitter instances
This commit is contained in:
		
							parent
							
								
									1b8211a341
								
							
						
					
					
						commit
						2caa24c79b
					
				
					 7 changed files with 268 additions and 3 deletions
				
			
		|  | @ -76,6 +76,7 @@ dependencies { | ||||||
|     implementation "com.google.code.gson:gson:2.8.6" |     implementation "com.google.code.gson:gson:2.8.6" | ||||||
|     implementation 'com.squareup.retrofit2:retrofit:2.9.0' |     implementation 'com.squareup.retrofit2:retrofit:2.9.0' | ||||||
|     implementation 'com.squareup.retrofit2:converter-gson:2.9.0' |     implementation 'com.squareup.retrofit2:converter-gson:2.9.0' | ||||||
|  |     implementation 'com.squareup.retrofit2:converter-simplexml:2.9.0' | ||||||
|     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' |     implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' | ||||||
|     implementation 'androidx.preference:preference:1.2.0' |     implementation 'androidx.preference:preference:1.2.0' | ||||||
|     implementation "org.conscrypt:conscrypt-android:2.5.2" |     implementation "org.conscrypt:conscrypt-android:2.5.2" | ||||||
|  |  | ||||||
|  | @ -22,6 +22,8 @@ import app.fedilab.android.client.entities.api.Marker; | ||||||
| import app.fedilab.android.client.entities.api.MastodonList; | import app.fedilab.android.client.entities.api.MastodonList; | ||||||
| import app.fedilab.android.client.entities.api.Status; | import app.fedilab.android.client.entities.api.Status; | ||||||
| import app.fedilab.android.client.entities.misskey.MisskeyNote; | import app.fedilab.android.client.entities.misskey.MisskeyNote; | ||||||
|  | import app.fedilab.android.client.entities.nitter.Nitter; | ||||||
|  | import app.fedilab.android.client.entities.nitter.NitterAccount; | ||||||
| import app.fedilab.android.client.entities.peertube.PeertubeVideo; | import app.fedilab.android.client.entities.peertube.PeertubeVideo; | ||||||
| import retrofit2.Call; | import retrofit2.Call; | ||||||
| import retrofit2.http.Body; | import retrofit2.http.Body; | ||||||
|  | @ -224,6 +226,17 @@ public interface MastodonTimelinesService { | ||||||
|             @Query("count") int count |             @Query("count") int count | ||||||
|     ); |     ); | ||||||
| 
 | 
 | ||||||
|  |     @GET("{names}/rss") | ||||||
|  |     Call<Nitter> getNitter( | ||||||
|  |             @Path("names") String id, | ||||||
|  |             @Query("max_position") String max_position | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     @GET("{account}/rss") | ||||||
|  |     Call<NitterAccount> getNitterAccount( | ||||||
|  |             @Path("account") String account | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|     @GET("api/v1/videos/{id}") |     @GET("api/v1/videos/{id}") | ||||||
|     Call<PeertubeVideo.Video> getPeertubeVideo( |     Call<PeertubeVideo.Video> getPeertubeVideo( | ||||||
|             @Path("id") String id |             @Path("id") String id | ||||||
|  |  | ||||||
|  | @ -0,0 +1,136 @@ | ||||||
|  | package app.fedilab.android.client.entities.nitter; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import android.content.Context; | ||||||
|  | 
 | ||||||
|  | import com.google.gson.Gson; | ||||||
|  | import com.google.gson.GsonBuilder; | ||||||
|  | 
 | ||||||
|  | import org.simpleframework.xml.Element; | ||||||
|  | import org.simpleframework.xml.ElementList; | ||||||
|  | import org.simpleframework.xml.Root; | ||||||
|  | 
 | ||||||
|  | import java.io.Serializable; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Date; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  | import java.util.regex.Matcher; | ||||||
|  | import java.util.regex.Pattern; | ||||||
|  | 
 | ||||||
|  | import app.fedilab.android.client.endpoints.MastodonTimelinesService; | ||||||
|  | import app.fedilab.android.client.entities.api.Attachment; | ||||||
|  | import app.fedilab.android.client.entities.api.Status; | ||||||
|  | import app.fedilab.android.helper.Helper; | ||||||
|  | import okhttp3.OkHttpClient; | ||||||
|  | import retrofit2.Call; | ||||||
|  | import retrofit2.Response; | ||||||
|  | import retrofit2.Retrofit; | ||||||
|  | import retrofit2.converter.gson.GsonConverterFactory; | ||||||
|  | import retrofit2.converter.simplexml.SimpleXmlConverterFactory; | ||||||
|  | 
 | ||||||
|  | @Root(name = "rss", strict = false) | ||||||
|  | public class Nitter implements Serializable { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     public static HashMap<String, NitterAccount> accounts = new HashMap<>(); | ||||||
|  |     @Element(name = "channel") | ||||||
|  |     public Channel channel; | ||||||
|  | 
 | ||||||
|  |     public static MastodonTimelinesService initInstanceXMLOnly(Context context, String instance) { | ||||||
|  |         Gson gson = new GsonBuilder().setDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").create(); | ||||||
|  |         OkHttpClient okHttpClient = new OkHttpClient.Builder() | ||||||
|  |                 .readTimeout(60, TimeUnit.SECONDS) | ||||||
|  |                 .connectTimeout(60, TimeUnit.SECONDS) | ||||||
|  |                 .callTimeout(60, TimeUnit.SECONDS) | ||||||
|  |                 .proxy(Helper.getProxy(context)) | ||||||
|  |                 .build(); | ||||||
|  |         Retrofit retrofit = new Retrofit.Builder() | ||||||
|  |                 .baseUrl("https://" + instance) | ||||||
|  |                 .addConverterFactory(GsonConverterFactory.create(gson)) | ||||||
|  |                 .addConverterFactory(SimpleXmlConverterFactory.create()) | ||||||
|  |                 .client(okHttpClient) | ||||||
|  |                 .build(); | ||||||
|  |         return retrofit.create(MastodonTimelinesService.class); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static Status convert(Context context, String instance, FeedItem feedItem) { | ||||||
|  |         Status status = new Status(); | ||||||
|  |         status.id = feedItem.pubDate.toString(); | ||||||
|  |         status.content = feedItem.title; | ||||||
|  |         status.text = feedItem.title; | ||||||
|  |         status.visibility = "public"; | ||||||
|  |         status.created_at = feedItem.pubDate; | ||||||
|  |         status.uri = feedItem.guid; | ||||||
|  |         status.url = feedItem.link; | ||||||
|  |         if (feedItem.creator != null && !accounts.containsValue(feedItem.creator)) { | ||||||
|  |             MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly(context, instance); | ||||||
|  |             Call<NitterAccount> accountCall = mastodonTimelinesService.getNitterAccount(instance); | ||||||
|  |             if (accountCall != null) { | ||||||
|  |                 try { | ||||||
|  |                     Response<NitterAccount> publicTlResponse = accountCall.execute(); | ||||||
|  |                     if (publicTlResponse.isSuccessful()) { | ||||||
|  |                         NitterAccount nitterAccount = publicTlResponse.body(); | ||||||
|  |                         accounts.put(feedItem.creator, nitterAccount); | ||||||
|  |                     } | ||||||
|  |                 } catch (Exception e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         NitterAccount nitterAccount = accounts.get(feedItem.creator); | ||||||
|  |         if (nitterAccount != null) { | ||||||
|  |             app.fedilab.android.client.entities.api.Account account = new app.fedilab.android.client.entities.api.Account(); | ||||||
|  |             String[] names = nitterAccount.channel.image.title.split("/"); | ||||||
|  |             account.id = feedItem.guid; | ||||||
|  |             account.acct = names[1]; | ||||||
|  |             account.username = names[1]; | ||||||
|  |             account.display_name = names[0]; | ||||||
|  |             account.avatar = nitterAccount.channel.image.url; | ||||||
|  |             account.avatar_static = nitterAccount.channel.image.url; | ||||||
|  |             account.url = nitterAccount.channel.image.link; | ||||||
|  |             status.account = account; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         Pattern imgPattern = Pattern.compile("<img [^>]*src=\"([^\"]+)\"[^>]*>"); | ||||||
|  |         Matcher matcher = imgPattern.matcher(feedItem.description); | ||||||
|  |         String description = feedItem.description; | ||||||
|  |         ArrayList<Attachment> attachmentList = new ArrayList<>(); | ||||||
|  |         while (matcher.find()) { | ||||||
|  |             description = description.replaceAll(Pattern.quote(matcher.group()), ""); | ||||||
|  |             Attachment attachment = new Attachment(); | ||||||
|  |             attachment.type = "image"; | ||||||
|  |             attachment.url = matcher.group(1); | ||||||
|  |             attachment.preview_url = matcher.group(1); | ||||||
|  |             attachment.id = matcher.group(1); | ||||||
|  |             attachmentList.add(attachment); | ||||||
|  |         } | ||||||
|  |         status.media_attachments = attachmentList; | ||||||
|  | 
 | ||||||
|  |         return status; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Root(name = "channel", strict = false) | ||||||
|  |     public static class Channel implements Serializable { | ||||||
|  |         @ElementList(name = "item") | ||||||
|  |         public List<FeedItem> mFeedItems; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Root(name = "item", strict = false) | ||||||
|  |     public static class FeedItem implements Serializable { | ||||||
|  |         @ElementList(name = "dc:creator", required = false) | ||||||
|  |         public String creator; | ||||||
|  |         @ElementList(name = "title") | ||||||
|  |         public String title; | ||||||
|  |         @ElementList(name = "description", required = false) | ||||||
|  |         public String description; | ||||||
|  |         @ElementList(name = "pubDate") | ||||||
|  |         public Date pubDate; | ||||||
|  |         @ElementList(name = "guid", required = false) | ||||||
|  |         public String guid; | ||||||
|  |         @ElementList(name = "link", required = false) | ||||||
|  |         public String link; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -0,0 +1,36 @@ | ||||||
|  | package app.fedilab.android.client.entities.nitter; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | import org.simpleframework.xml.Element; | ||||||
|  | import org.simpleframework.xml.Root; | ||||||
|  | 
 | ||||||
|  | import java.io.Serializable; | ||||||
|  | import java.util.HashMap; | ||||||
|  | 
 | ||||||
|  | @Root(name = "rss", strict = false) | ||||||
|  | public class NitterAccount implements Serializable { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     public static HashMap<String, NitterAccount> accounts = new HashMap<>(); | ||||||
|  |     @Element(name = "channel") | ||||||
|  |     public Channel channel; | ||||||
|  | 
 | ||||||
|  |     @Root(name = "channel", strict = false) | ||||||
|  |     public static class Channel implements Serializable { | ||||||
|  |         @Element(name = "image") | ||||||
|  |         public Image image; | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Root(name = "image", strict = false) | ||||||
|  |     public static class Image implements Serializable { | ||||||
|  |         @Element(name = "title") | ||||||
|  |         public String title; | ||||||
|  |         @Element(name = "url") | ||||||
|  |         public String url; | ||||||
|  |         @Element(name = "link") | ||||||
|  |         public String link; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | @ -447,7 +447,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | ||||||
|                 attachment.peertubeId = matcherLink.group(3); |                 attachment.peertubeId = matcherLink.group(3); | ||||||
|                 attachmentList.add(attachment); |                 attachmentList.add(attachment); | ||||||
|                 statusToDeal.media_attachments = attachmentList; |                 statusToDeal.media_attachments = attachmentList; | ||||||
|                 adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); |                 //adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -21,6 +21,7 @@ import android.content.BroadcastReceiver; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| import android.content.IntentFilter; | import android.content.IntentFilter; | ||||||
|  | import android.content.SharedPreferences; | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.os.Handler; | import android.os.Handler; | ||||||
| import android.os.Looper; | import android.os.Looper; | ||||||
|  | @ -33,6 +34,7 @@ import androidx.annotation.Nullable; | ||||||
| import androidx.fragment.app.Fragment; | import androidx.fragment.app.Fragment; | ||||||
| import androidx.lifecycle.ViewModelProvider; | import androidx.lifecycle.ViewModelProvider; | ||||||
| import androidx.localbroadcastmanager.content.LocalBroadcastManager; | import androidx.localbroadcastmanager.content.LocalBroadcastManager; | ||||||
|  | import androidx.preference.PreferenceManager; | ||||||
| import androidx.recyclerview.widget.LinearLayoutManager; | import androidx.recyclerview.widget.LinearLayoutManager; | ||||||
| import androidx.recyclerview.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| 
 | 
 | ||||||
|  | @ -204,7 +206,12 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | ||||||
|             searchCache = getArguments().getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null); |             searchCache = getArguments().getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null); | ||||||
|             pinnedTimeline = (PinnedTimeline) getArguments().getSerializable(Helper.ARG_REMOTE_INSTANCE); |             pinnedTimeline = (PinnedTimeline) getArguments().getSerializable(Helper.ARG_REMOTE_INSTANCE); | ||||||
|             if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) { |             if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) { | ||||||
|                 remoteInstance = pinnedTimeline.remoteInstance.host; |                 if (pinnedTimeline.remoteInstance.type != RemoteInstance.InstanceType.NITTER) { | ||||||
|  |                     remoteInstance = pinnedTimeline.remoteInstance.host; | ||||||
|  |                 } else { | ||||||
|  |                     SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); | ||||||
|  |                     remoteInstance = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             tagTimeline = (TagTimeline) getArguments().getSerializable(Helper.ARG_TAG_TIMELINE); |             tagTimeline = (TagTimeline) getArguments().getSerializable(Helper.ARG_TAG_TIMELINE); | ||||||
|  | @ -642,7 +649,24 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. | ||||||
| 
 | 
 | ||||||
|                         //NITTER TIMELINES |                         //NITTER TIMELINES | ||||||
|                         if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) { |                         if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) { | ||||||
| 
 |                             if (direction == null) { | ||||||
|  |                                 timelinesVM.getNitter(remoteInstance, pinnedTimeline.remoteInstance.host, null) | ||||||
|  |                                         .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); | ||||||
|  |                             } else if (direction == DIRECTION.BOTTOM) { | ||||||
|  |                                 timelinesVM.getNitter(remoteInstance, pinnedTimeline.remoteInstance.host, max_id) | ||||||
|  |                                         .observe(getViewLifecycleOwner(), statusesBottom -> dealWithPagination(statusesBottom, DIRECTION.BOTTOM, false)); | ||||||
|  |                             } else if (direction == DIRECTION.TOP) { | ||||||
|  |                                 flagLoading = false; | ||||||
|  |                             } else if (direction == DIRECTION.REFRESH) { | ||||||
|  |                                 timelinesVM.getNitter(remoteInstance, pinnedTimeline.remoteInstance.host, null) | ||||||
|  |                                         .observe(getViewLifecycleOwner(), statusesRefresh -> { | ||||||
|  |                                             if (statusAdapter != null) { | ||||||
|  |                                                 dealWithPagination(statusesRefresh, DIRECTION.REFRESH, true); | ||||||
|  |                                             } else { | ||||||
|  |                                                 initializeStatusesCommonView(statusesRefresh); | ||||||
|  |                                             } | ||||||
|  |                                         }); | ||||||
|  |                             } | ||||||
|                         } //GNU TIMELINES |                         } //GNU TIMELINES | ||||||
|                         else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.GNU) { |                         else if (pinnedTimeline != null && pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.GNU) { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -45,6 +45,7 @@ import app.fedilab.android.client.entities.app.BaseAccount; | ||||||
| import app.fedilab.android.client.entities.app.StatusCache; | import app.fedilab.android.client.entities.app.StatusCache; | ||||||
| import app.fedilab.android.client.entities.app.StatusDraft; | import app.fedilab.android.client.entities.app.StatusDraft; | ||||||
| import app.fedilab.android.client.entities.misskey.MisskeyNote; | import app.fedilab.android.client.entities.misskey.MisskeyNote; | ||||||
|  | import app.fedilab.android.client.entities.nitter.Nitter; | ||||||
| import app.fedilab.android.client.entities.peertube.PeertubeVideo; | import app.fedilab.android.client.entities.peertube.PeertubeVideo; | ||||||
| import app.fedilab.android.exception.DBException; | import app.fedilab.android.exception.DBException; | ||||||
| import app.fedilab.android.helper.Helper; | import app.fedilab.android.helper.Helper; | ||||||
|  | @ -56,6 +57,7 @@ import retrofit2.Call; | ||||||
| import retrofit2.Response; | import retrofit2.Response; | ||||||
| import retrofit2.Retrofit; | import retrofit2.Retrofit; | ||||||
| import retrofit2.converter.gson.GsonConverterFactory; | import retrofit2.converter.gson.GsonConverterFactory; | ||||||
|  | import retrofit2.converter.simplexml.SimpleXmlConverterFactory; | ||||||
| 
 | 
 | ||||||
| public class TimelinesVM extends AndroidViewModel { | public class TimelinesVM extends AndroidViewModel { | ||||||
| 
 | 
 | ||||||
|  | @ -91,6 +93,17 @@ public class TimelinesVM extends AndroidViewModel { | ||||||
|         return retrofit.create(MastodonTimelinesService.class); |         return retrofit.create(MastodonTimelinesService.class); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private MastodonTimelinesService initInstanceXMLOnly(String instance) { | ||||||
|  |         Gson gson = new GsonBuilder().setDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz").create(); | ||||||
|  |         Retrofit retrofit = new Retrofit.Builder() | ||||||
|  |                 .baseUrl("https://" + instance) | ||||||
|  |                 //.addConverterFactory(GsonConverterFactory.create(gson)) | ||||||
|  |                 .addConverterFactory(SimpleXmlConverterFactory.create()) | ||||||
|  |                 .client(okHttpClient) | ||||||
|  |                 .build(); | ||||||
|  |         return retrofit.create(MastodonTimelinesService.class); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     private MastodonTimelinesService init(String instance) { |     private MastodonTimelinesService init(String instance) { | ||||||
|         Gson gson = new GsonBuilder().setDateFormat("MMM dd, yyyy HH:mm:ss").create(); |         Gson gson = new GsonBuilder().setDateFormat("MMM dd, yyyy HH:mm:ss").create(); | ||||||
|         Retrofit retrofit = new Retrofit.Builder() |         Retrofit retrofit = new Retrofit.Builder() | ||||||
|  | @ -147,6 +160,48 @@ public class TimelinesVM extends AndroidViewModel { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Public timeline for Nitter | ||||||
|  |      * | ||||||
|  |      * @param max_position Return results older than this id | ||||||
|  |      * @return {@link LiveData} containing a {@link Statuses} | ||||||
|  |      */ | ||||||
|  |     public LiveData<Statuses> getNitter(@NonNull String instance, | ||||||
|  |                                         String accountsStr, | ||||||
|  |                                         String max_position) { | ||||||
|  |         MastodonTimelinesService mastodonTimelinesService = initInstanceXMLOnly(instance); | ||||||
|  |         statusesMutableLiveData = new MutableLiveData<>(); | ||||||
|  |         new Thread(() -> { | ||||||
|  |             Call<Nitter> publicTlCall = mastodonTimelinesService.getNitter(accountsStr, max_position); | ||||||
|  |             Statuses statuses = new Statuses(); | ||||||
|  |             if (publicTlCall != null) { | ||||||
|  |                 try { | ||||||
|  |                     Response<Nitter> publicTlResponse = publicTlCall.execute(); | ||||||
|  |                     if (publicTlResponse.isSuccessful()) { | ||||||
|  |                         Nitter rssResponse = publicTlResponse.body(); | ||||||
|  |                         List<Status> statusList = new ArrayList<>(); | ||||||
|  |                         if (rssResponse != null && rssResponse.channel != null && rssResponse.channel.mFeedItems != null) { | ||||||
|  |                             for (Nitter.FeedItem feedItem : rssResponse.channel.mFeedItems) { | ||||||
|  |                                 Status status = Nitter.convert(getApplication(), instance, feedItem); | ||||||
|  |                                 statusList.add(status); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                         List<Status> filteredStatuses = TimelineHelper.filterStatus(getApplication(), statusList, TimelineHelper.FilterTimeLineType.PUBLIC); | ||||||
|  |                         statuses.statuses = SpannableHelper.convertStatus(getApplication().getApplicationContext(), filteredStatuses); | ||||||
|  |                         statuses.pagination = MastodonHelper.getPagination(publicTlResponse.headers()); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                 } catch (Exception e) { | ||||||
|  |                     e.printStackTrace(); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             Handler mainHandler = new Handler(Looper.getMainLooper()); | ||||||
|  |             Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses); | ||||||
|  |             mainHandler.post(myRunnable); | ||||||
|  |         }).start(); | ||||||
|  |         return statusesMutableLiveData; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Public timeline for Misskey |      * Public timeline for Misskey | ||||||
|      * |      * | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue