Configure streaming per each tabs
* Can use in Home, Local, Federated * Process filter_changed event
This commit is contained in:
parent
0982834805
commit
831ec88b6f
|
@ -22,6 +22,7 @@ import android.content.Intent
|
|||
import android.content.res.ColorStateList
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.InsetDrawable
|
||||
import android.net.Uri
|
||||
|
@ -63,6 +64,7 @@ import com.keylesspalace.tusky.db.AccountEntity
|
|||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.fragment.NotificationsFragment
|
||||
import com.keylesspalace.tusky.fragment.SFragment
|
||||
import com.keylesspalace.tusky.fragment.TimelineFragment
|
||||
import com.keylesspalace.tusky.interfaces.AccountSelectionListener
|
||||
import com.keylesspalace.tusky.interfaces.ActionButtonActivity
|
||||
import com.keylesspalace.tusky.interfaces.ReselectableFragment
|
||||
|
@ -74,10 +76,14 @@ import com.mikepenz.materialdrawer.model.*
|
|||
import com.mikepenz.materialdrawer.model.interfaces.*
|
||||
import com.mikepenz.materialdrawer.util.*
|
||||
import com.mikepenz.materialdrawer.widget.AccountHeaderView
|
||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider
|
||||
import com.uber.autodispose.android.lifecycle.autoDispose
|
||||
import com.uber.autodispose.autoDispose
|
||||
import dagger.android.DispatchingAndroidInjector
|
||||
import dagger.android.HasAndroidInjector
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import net.accelf.yuito.FooterDrawerItem
|
||||
import net.accelf.yuito.QuickTootHelper
|
||||
|
@ -99,6 +105,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
private lateinit var header: AccountHeaderView
|
||||
private lateinit var drawerToggle: ActionBarDrawerToggle
|
||||
|
||||
private var streamingTabsCount = 0
|
||||
private var notificationTabPosition = 0
|
||||
|
||||
private var adapter: MainPagerAdapter? = null
|
||||
|
@ -179,7 +186,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
* drawer, though, because its callback touches the header in the drawer. */
|
||||
fetchUserInfo()
|
||||
|
||||
setupTabs(showNotificationTab)
|
||||
val popups = setupTabs(showNotificationTab)
|
||||
|
||||
val pageMargin = resources.getDimensionPixelSize(R.dimen.tab_page_margin)
|
||||
viewPager.setPageTransformer(MarginPageTransformer(pageMargin))
|
||||
|
@ -192,66 +199,6 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
viewPager.offscreenPageLimit = 9
|
||||
}
|
||||
|
||||
val popups = ArrayList<PopupMenu>()
|
||||
for (i in 0 until tabLayout.tabCount) {
|
||||
val tab = tabLayout.getTabAt(i)!!
|
||||
val popup = PopupMenu(this, tab.view)
|
||||
popup.menuInflater.inflate(R.menu.view_tab_action, popup.menu)
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
if (popup.menu is MenuBuilder) {
|
||||
val menuBuilder = popup.menu as MenuBuilder
|
||||
if (tab.position == notificationTabPosition) {
|
||||
menuBuilder.findItem(R.id.tabToggleNotificationsFilter).isVisible = true
|
||||
}
|
||||
menuBuilder.setOptionalIconsVisible(true)
|
||||
menuBuilder.visibleItems.forEach { item ->
|
||||
val iconMarginPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics).toInt()
|
||||
|
||||
if (item.icon != null) {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
|
||||
item.icon = InsetDrawable(item.icon, iconMarginPx, 0, iconMarginPx, 0)
|
||||
} else {
|
||||
item.icon = object : InsetDrawable(item.icon, iconMarginPx, 0, iconMarginPx, 0) {
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
return intrinsicHeight + iconMarginPx + iconMarginPx
|
||||
}
|
||||
}
|
||||
}
|
||||
ThemeUtils.setDrawableTint(this, item.icon, android.R.attr.textColorPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
val fragment = adapter?.getFragment(tab.position)
|
||||
when (item.itemId) {
|
||||
R.id.tabJumpToTop -> {
|
||||
if (fragment is ReselectableFragment) {
|
||||
(fragment as ReselectableFragment).onReselect()
|
||||
}
|
||||
}
|
||||
R.id.tabReset -> {
|
||||
if (fragment is ReselectableFragment) {
|
||||
(fragment as ReselectableFragment).onReset()
|
||||
}
|
||||
}
|
||||
R.id.tabToggleNotificationsFilter -> {
|
||||
if (fragment is NotificationsFragment) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
prefs.edit().putBoolean("showNotificationsFilter",
|
||||
!prefs.getBoolean("showNotificationsFilter", true))
|
||||
.apply()
|
||||
eventHub.dispatch(PreferenceChangedEvent("showNotificationsFilter"))
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
popups.add(popup)
|
||||
}
|
||||
|
||||
tabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
|
||||
override fun onTabSelected(tab: TabLayout.Tab) {
|
||||
if (tab.position == notificationTabPosition) {
|
||||
|
@ -294,21 +241,19 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
startStreaming()
|
||||
keepScreenOn()
|
||||
}
|
||||
|
||||
private fun startStreaming() {
|
||||
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean("useHTLStream", false)) {
|
||||
private fun keepScreenOn() {
|
||||
if (streamingTabsCount > 0) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
} else {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
stopStreaming()
|
||||
}
|
||||
|
||||
private fun stopStreaming() {
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
|
@ -575,12 +520,22 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
super.onSaveInstanceState(mainDrawer.saveInstanceState(outState))
|
||||
}
|
||||
|
||||
private fun setupTabs(selectNotificationTab: Boolean) {
|
||||
val tabs = accountManager.activeAccount!!.tabPreferences
|
||||
private fun tintCheckIcon(item: MenuItem) {
|
||||
if (item.isChecked) {
|
||||
@Suppress("DEPRECATION")
|
||||
item.icon.setColorFilter(ContextCompat.getColor(this, R.color.tusky_green_light), PorterDuff.Mode.SRC_IN)
|
||||
} else {
|
||||
ThemeUtils.setDrawableTint(this, item.icon, android.R.attr.textColorTertiary)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupTabs(selectNotificationTab: Boolean): ArrayList<PopupMenu> {
|
||||
val tabs = accountManager.activeAccount!!.tabPreferences.toMutableList()
|
||||
adapter = MainPagerAdapter(tabs, this)
|
||||
viewPager.adapter = adapter
|
||||
TabLayoutMediator(tabLayout, viewPager, TabConfigurationStrategy { _: TabLayout.Tab?, _: Int -> }).attach()
|
||||
tabLayout.removeAllTabs()
|
||||
val popups = ArrayList<PopupMenu>()
|
||||
for (i in tabs.indices) {
|
||||
val tab = tabLayout.newTab()
|
||||
.setIcon(tabs[i].icon)
|
||||
|
@ -590,13 +545,110 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
tab.setContentDescription(tabs[i].text)
|
||||
}
|
||||
tabLayout.addTab(tab)
|
||||
|
||||
val popup = PopupMenu(this, tab.view)
|
||||
popup.menuInflater.inflate(R.menu.view_tab_action, popup.menu)
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
if (popup.menu is MenuBuilder) {
|
||||
val menuBuilder = popup.menu as MenuBuilder
|
||||
|
||||
if (tabs[i].id in arrayOf(HOME, LOCAL, FEDERATED)) {
|
||||
menuBuilder.findItem(R.id.tabToggleStreaming).apply {
|
||||
isVisible = true
|
||||
isChecked = tabs[i].enableStreaming
|
||||
}
|
||||
}
|
||||
if (tabs[i].id == NOTIFICATIONS) {
|
||||
menuBuilder.findItem(R.id.tabToggleNotificationsFilter).isVisible = true
|
||||
}
|
||||
|
||||
menuBuilder.setOptionalIconsVisible(true)
|
||||
menuBuilder.visibleItems.forEach { item ->
|
||||
val iconMarginPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8f, resources.displayMetrics).toInt()
|
||||
|
||||
if (item.icon != null) {
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
|
||||
item.icon = InsetDrawable(item.icon, iconMarginPx, 0, iconMarginPx, 0)
|
||||
} else {
|
||||
item.icon = object : InsetDrawable(item.icon, iconMarginPx, 0, iconMarginPx, 0) {
|
||||
override fun getIntrinsicWidth(): Int {
|
||||
return intrinsicHeight + iconMarginPx + iconMarginPx
|
||||
}
|
||||
}
|
||||
}
|
||||
ThemeUtils.setDrawableTint(this, item.icon, android.R.attr.textColorPrimary)
|
||||
}
|
||||
}
|
||||
tintCheckIcon(menuBuilder.findItem(R.id.tabToggleStreaming))
|
||||
}
|
||||
|
||||
popup.setOnMenuItemClickListener { item ->
|
||||
val fragment = adapter?.getFragment(tab.position)
|
||||
when (item.itemId) {
|
||||
R.id.tabJumpToTop -> {
|
||||
if (fragment is ReselectableFragment) {
|
||||
(fragment as ReselectableFragment).onReselect()
|
||||
}
|
||||
}
|
||||
R.id.tabReset -> {
|
||||
if (fragment is ReselectableFragment) {
|
||||
(fragment as ReselectableFragment).onReset()
|
||||
}
|
||||
}
|
||||
R.id.tabToggleStreaming -> {
|
||||
if (fragment is TimelineFragment) {
|
||||
fragment.streamingEnabled = !fragment.streamingEnabled
|
||||
item.isChecked = fragment.streamingEnabled
|
||||
tintCheckIcon(item)
|
||||
|
||||
if (fragment.streamingEnabled) {
|
||||
streamingTabsCount++
|
||||
} else {
|
||||
streamingTabsCount--
|
||||
}
|
||||
keepScreenOn()
|
||||
|
||||
tabs[i] = tabs[i].copy(enableStreaming = fragment.streamingEnabled)
|
||||
accountManager.activeAccount?.let {
|
||||
Single.fromCallable {
|
||||
it.tabPreferences = tabs
|
||||
accountManager.saveAccount(it)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.autoDispose(AndroidLifecycleScopeProvider.from(this, Lifecycle.Event.ON_DESTROY))
|
||||
.subscribe()
|
||||
}
|
||||
}
|
||||
}
|
||||
R.id.tabToggleNotificationsFilter -> {
|
||||
if (fragment is NotificationsFragment) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
prefs.edit().putBoolean("showNotificationsFilter",
|
||||
!prefs.getBoolean("showNotificationsFilter", true))
|
||||
.apply()
|
||||
eventHub.dispatch(PreferenceChangedEvent("showNotificationsFilter"))
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
popups.add(popup)
|
||||
|
||||
if (tabs[i].id == NOTIFICATIONS) {
|
||||
notificationTabPosition = i
|
||||
if (selectNotificationTab) {
|
||||
tab.select()
|
||||
}
|
||||
}
|
||||
|
||||
if (tabs[i].enableStreaming) {
|
||||
streamingTabsCount++
|
||||
}
|
||||
}
|
||||
keepScreenOn()
|
||||
return popups
|
||||
}
|
||||
|
||||
private fun handleProfileClick(profile: IProfile, current: Boolean): Boolean {
|
||||
|
@ -622,7 +674,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, HasAndroidInje
|
|||
cacheUpdater.stop()
|
||||
SFragment.flushFilters()
|
||||
accountManager.setActiveAccount(newSelectedId)
|
||||
stopStreaming()
|
||||
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
|
||||
if (forward != null) {
|
||||
|
|
|
@ -32,18 +32,22 @@ const val DIRECT = "Direct"
|
|||
const val HASHTAG = "Hashtag"
|
||||
const val LIST = "List"
|
||||
|
||||
const val STREAMING = "STR"
|
||||
|
||||
data class TabData(val id: String,
|
||||
@StringRes val text: Int,
|
||||
@DrawableRes val icon: Int,
|
||||
val fragment: (List<String>) -> Fragment,
|
||||
val arguments: List<String> = emptyList())
|
||||
val arguments: List<String> = emptyList(),
|
||||
val enableStreaming: Boolean = false)
|
||||
|
||||
fun createTabDataFromId(id: String, arguments: List<String> = emptyList()): TabData {
|
||||
return when (id) {
|
||||
HOME -> TabData(HOME, R.string.title_home, R.drawable.ic_home_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.HOME) })
|
||||
val enableStreaming = id.endsWith(STREAMING)
|
||||
return when (if (enableStreaming) id.slice(IntRange(0, id.length - 4)) else id) {
|
||||
HOME -> TabData(HOME, R.string.title_home, R.drawable.ic_home_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.HOME, enableStreaming) }, enableStreaming = enableStreaming)
|
||||
NOTIFICATIONS -> TabData(NOTIFICATIONS, R.string.title_notifications, R.drawable.ic_notifications_24dp, { NotificationsFragment.newInstance() })
|
||||
LOCAL -> TabData(LOCAL, R.string.title_public_local, R.drawable.ic_local_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_LOCAL) })
|
||||
FEDERATED -> TabData(FEDERATED, R.string.title_public_federated, R.drawable.ic_public_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_FEDERATED) })
|
||||
LOCAL -> TabData(LOCAL, R.string.title_public_local, R.drawable.ic_local_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_LOCAL, enableStreaming) }, enableStreaming = enableStreaming)
|
||||
FEDERATED -> TabData(FEDERATED, R.string.title_public_federated, R.drawable.ic_public_24dp, { TimelineFragment.newInstance(TimelineFragment.Kind.PUBLIC_FEDERATED, enableStreaming) }, enableStreaming = enableStreaming)
|
||||
DIRECT -> TabData(DIRECT, R.string.title_direct_messages, R.drawable.ic_reblog_direct_24dp, { ConversationsFragment.newInstance() })
|
||||
HASHTAG -> TabData(HASHTAG, R.string.hashtags, R.drawable.ic_hashtag, { args -> TimelineFragment.newHashtagInstance(args) }, arguments)
|
||||
LIST -> TabData(LIST, R.string.list, R.drawable.ic_list, { args -> TimelineFragment.newInstance(TimelineFragment.Kind.LIST, args.getOrNull(0).orEmpty()) }, arguments)
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.keylesspalace.tusky.TabData
|
|||
import com.keylesspalace.tusky.entity.Account
|
||||
import com.keylesspalace.tusky.entity.Poll
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.fragment.TimelineFragment
|
||||
|
||||
data class FavoriteEvent(val statusId: String, val favourite: Boolean) : Dispatchable
|
||||
data class ReblogEvent(val statusId: String, val reblog: Boolean) : Dispatchable
|
||||
|
@ -21,5 +22,5 @@ data class MainTabsChangedEvent(val newTabs: List<TabData>) : Dispatchable
|
|||
data class PollVoteEvent(val statusId: String, val poll: Poll) : Dispatchable
|
||||
data class DomainMuteEvent(val instance: String): Dispatchable
|
||||
data class QuickReplyEvent(val status: Status) : Dispatchable
|
||||
data class StreamUpdateEvent(val status: Status, val first: Boolean) : Dispatchable
|
||||
data class StreamUpdateEvent(val status: Status, val targetKind: TimelineFragment.Kind, val first: Boolean) : Dispatchable
|
||||
data class DrawerFooterClickedEvent(val placeholder: Boolean) : Dispatchable
|
|
@ -21,6 +21,7 @@ import androidx.core.text.toHtml
|
|||
import androidx.room.TypeConverter
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.keylesspalace.tusky.STREAMING
|
||||
import com.keylesspalace.tusky.TabData
|
||||
import com.keylesspalace.tusky.components.conversation.ConversationAccountEntity
|
||||
import com.keylesspalace.tusky.createTabDataFromId
|
||||
|
@ -72,7 +73,10 @@ class Converters {
|
|||
@TypeConverter
|
||||
fun tabDataToString(tabData: List<TabData>?): String? {
|
||||
// List name may include ":"
|
||||
return tabData?.joinToString(";") { it.id + ":" + it.arguments.joinToString(":") { s -> URLEncoder.encode(s, "UTF-8") } }
|
||||
return tabData?.joinToString(";") {
|
||||
(if (it.enableStreaming) { it.id + STREAMING } else { it.id }) + ":" +
|
||||
it.arguments.joinToString(":") { s -> URLEncoder.encode(s, "UTF-8") }
|
||||
}
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
|
|
|
@ -7,14 +7,16 @@ data class StreamEvent(
|
|||
var payload: String
|
||||
) {
|
||||
|
||||
enum class EventType(val num: Int) {
|
||||
UNKNOWN(0),
|
||||
enum class EventType {
|
||||
UNKNOWN,
|
||||
@SerializedName("update")
|
||||
UPDATE(1),
|
||||
UPDATE,
|
||||
@SerializedName("notification")
|
||||
NOTIFICATION(2),
|
||||
NOTIFICATION,
|
||||
@SerializedName("delete")
|
||||
DELETE(3);
|
||||
DELETE,
|
||||
@SerializedName("filters_changed")
|
||||
FILTERS_CHANGED;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,6 +133,7 @@ public class TimelineFragment extends SFragment implements
|
|||
private static final String ID_ARG = "id";
|
||||
private static final String HASHTAGS_ARG = "hastags";
|
||||
private static final String ARG_ENABLE_SWIPE_TO_REFRESH = "arg.enable.swipe.to.refresh";
|
||||
private static final String ARG_ENABLE_STREAMING = "arg.enable.stream";
|
||||
|
||||
private static final int LOAD_AT_ONCE = 30;
|
||||
private boolean isSwipeToRefreshEnabled = true;
|
||||
|
@ -193,6 +194,7 @@ public class TimelineFragment extends SFragment implements
|
|||
private boolean reduceTimelineLoading;
|
||||
private boolean checkMobileNetwork;
|
||||
|
||||
private boolean streamingEnabled;
|
||||
private WebSocket webSocket;
|
||||
|
||||
private PairedList<Either<Placeholder, Status>, StatusViewData> statuses =
|
||||
|
@ -217,16 +219,25 @@ public class TimelineFragment extends SFragment implements
|
|||
return newInstance(kind, null);
|
||||
}
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind, boolean enableStreaming) {
|
||||
return newInstance(kind, null, true, enableStreaming);
|
||||
}
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind, @Nullable String hashtagOrId) {
|
||||
return newInstance(kind, hashtagOrId, true);
|
||||
}
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind, @Nullable String hashtagOrId, boolean enableSwipeToRefresh) {
|
||||
return newInstance(kind, hashtagOrId, enableSwipeToRefresh, false);
|
||||
}
|
||||
|
||||
public static TimelineFragment newInstance(Kind kind, @Nullable String hashtagOrId, boolean enableSwipeToRefresh, boolean enableStreaming) {
|
||||
TimelineFragment fragment = new TimelineFragment();
|
||||
Bundle arguments = new Bundle(3);
|
||||
arguments.putString(KIND_ARG, kind.name());
|
||||
arguments.putString(ID_ARG, hashtagOrId);
|
||||
arguments.putBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, enableSwipeToRefresh);
|
||||
arguments.putBoolean(ARG_ENABLE_STREAMING, enableStreaming);
|
||||
fragment.setArguments(arguments);
|
||||
return fragment;
|
||||
}
|
||||
|
@ -272,6 +283,8 @@ public class TimelineFragment extends SFragment implements
|
|||
adapter = new TimelineAdapter(dataSource, statusDisplayOptions, this);
|
||||
|
||||
isSwipeToRefreshEnabled = arguments.getBoolean(ARG_ENABLE_SWIPE_TO_REFRESH, true);
|
||||
|
||||
streamingEnabled = arguments.getBoolean(ARG_ENABLE_STREAMING, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -306,7 +319,9 @@ public class TimelineFragment extends SFragment implements
|
|||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
startStreaming();
|
||||
if (streamingEnabled) {
|
||||
startStreaming();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -316,35 +331,41 @@ public class TimelineFragment extends SFragment implements
|
|||
}
|
||||
|
||||
private void startStreaming() {
|
||||
if (preferences.getBoolean("useHTLStream", false) && kind == Kind.HOME) {
|
||||
connectWebsocket(buildStreamingUrl());
|
||||
}
|
||||
}
|
||||
|
||||
private String buildStreamingUrl() {
|
||||
AccountEntity activeAccount = accountManager.getActiveAccount();
|
||||
if (activeAccount != null) {
|
||||
return "wss://" + activeAccount.getDomain() + "/api/v1/streaming/?" + "stream=user" + "&" + "access_token" + "=" + activeAccount.getAccessToken();
|
||||
} else {
|
||||
return null;
|
||||
String endpoint = "wss://" + activeAccount.getDomain() + "/api/v1/streaming/"+ "?"
|
||||
+ "access_token" + "=" + activeAccount.getAccessToken() + "&"
|
||||
+ "stream" + "=";
|
||||
switch (kind) {
|
||||
case HOME: {
|
||||
endpoint += "user";
|
||||
break;
|
||||
}
|
||||
case PUBLIC_FEDERATED: {
|
||||
endpoint += "public";
|
||||
break;
|
||||
}
|
||||
case PUBLIC_LOCAL: {
|
||||
endpoint += "public:local";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (webSocket != null) {
|
||||
stopStreaming();
|
||||
}
|
||||
|
||||
Request request = new Request.Builder().url(endpoint).build();
|
||||
|
||||
OkHttpClient client = new OkHttpClient.Builder().build();
|
||||
|
||||
webSocket = client.newWebSocket(request, new TimelineStreamingListener(eventHub, kind));
|
||||
}
|
||||
}
|
||||
|
||||
private void connectWebsocket(String endpoint) {
|
||||
if (webSocket != null) {
|
||||
stopStreaming();
|
||||
}
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(endpoint)
|
||||
.build();
|
||||
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.build();
|
||||
|
||||
webSocket = client.newWebSocket(request, new TimelineStreamingListener(eventHub));
|
||||
}
|
||||
|
||||
private void stopStreaming() {
|
||||
if (webSocket == null) {
|
||||
return;
|
||||
|
@ -353,6 +374,19 @@ public class TimelineFragment extends SFragment implements
|
|||
webSocket = null;
|
||||
}
|
||||
|
||||
public boolean getStreamingEnabled() {
|
||||
return streamingEnabled;
|
||||
}
|
||||
|
||||
public void setStreamingEnabled(boolean streamingEnabled) {
|
||||
this.streamingEnabled = streamingEnabled;
|
||||
if (streamingEnabled) {
|
||||
startStreaming();
|
||||
} else {
|
||||
stopStreaming();
|
||||
}
|
||||
}
|
||||
|
||||
private void sendInitialRequest() {
|
||||
if (this.kind == Kind.HOME) {
|
||||
this.tryCache();
|
||||
|
@ -628,7 +662,7 @@ public class TimelineFragment extends SFragment implements
|
|||
} else if (event instanceof PreferenceChangedEvent) {
|
||||
onPreferenceChanged(((PreferenceChangedEvent) event).getPreferenceKey());
|
||||
} else if (event instanceof StreamUpdateEvent) {
|
||||
if (kind == Kind.HOME) {
|
||||
if (streamingEnabled) {
|
||||
handleStreamUpdateEvent((StreamUpdateEvent) event);
|
||||
}
|
||||
}
|
||||
|
@ -1360,7 +1394,9 @@ public class TimelineFragment extends SFragment implements
|
|||
if (findStatusOrReblogPositionById(status.getId()) < 0) {
|
||||
statuses.add(0, item);
|
||||
updateAdapter();
|
||||
timelineRepo.addSingleStatusToDb(status);
|
||||
if (kind == Kind.HOME) {
|
||||
timelineRepo.addSingleStatusToDb(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -1464,11 +1500,12 @@ public class TimelineFragment extends SFragment implements
|
|||
}
|
||||
|
||||
private void handleStatusComposeEvent(@NonNull Status status) {
|
||||
if (streamingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (kind) {
|
||||
case HOME:
|
||||
if (preferences.getBoolean("useHTLStream", false)){
|
||||
return;
|
||||
}
|
||||
case PUBLIC_FEDERATED:
|
||||
case PUBLIC_LOCAL:
|
||||
break;
|
||||
|
@ -1505,6 +1542,10 @@ public class TimelineFragment extends SFragment implements
|
|||
}
|
||||
|
||||
private void handleStreamUpdateEvent(StreamUpdateEvent event) {
|
||||
if (event.getTargetKind() != kind) {
|
||||
return;
|
||||
}
|
||||
|
||||
Status status = event.getStatus();
|
||||
if (event.getFirst() && statuses.get(0).isRight()) {
|
||||
Placeholder placeholder = new Placeholder(statuses.get(0).asRight().getId() + 1);
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
package net.accelf.yuito;
|
||||
|
||||
import android.text.Spanned;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.keylesspalace.tusky.appstore.EventHub;
|
||||
import com.keylesspalace.tusky.appstore.StatusDeletedEvent;
|
||||
import com.keylesspalace.tusky.appstore.StreamUpdateEvent;
|
||||
import com.keylesspalace.tusky.entity.Status;
|
||||
import com.keylesspalace.tusky.entity.StreamEvent;
|
||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter;
|
||||
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
|
||||
public class TimelineStreamingListener extends WebSocketListener {
|
||||
|
||||
private Gson gson = buildGson();
|
||||
private boolean isFirstStatus = true;
|
||||
|
||||
private EventHub eventHub;
|
||||
|
||||
private static Gson buildGson() {
|
||||
return new GsonBuilder()
|
||||
.registerTypeAdapter(Spanned.class, new SpannedTypeAdapter())
|
||||
.create();
|
||||
}
|
||||
|
||||
public TimelineStreamingListener(EventHub eventHub) {
|
||||
this.eventHub = eventHub;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
Log.d("StreamingListener", "Stream connected.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
StreamEvent event = gson.fromJson(text, StreamEvent.class);
|
||||
|
||||
String payload = event.getPayload();
|
||||
switch (event.getEvent()) {
|
||||
case UPDATE:
|
||||
Status status = gson.fromJson(payload, Status.class);
|
||||
eventHub.dispatch(new StreamUpdateEvent(status, isFirstStatus));
|
||||
isFirstStatus = false;
|
||||
break;
|
||||
case DELETE:
|
||||
eventHub.dispatch(new StatusDeletedEvent(payload));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
Log.d("StreamingListener", "Stream closed.");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package net.accelf.yuito
|
||||
|
||||
import android.text.Spanned
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.keylesspalace.tusky.appstore.EventHub
|
||||
import com.keylesspalace.tusky.appstore.PreferenceChangedEvent
|
||||
import com.keylesspalace.tusky.appstore.StatusDeletedEvent
|
||||
import com.keylesspalace.tusky.appstore.StreamUpdateEvent
|
||||
import com.keylesspalace.tusky.entity.Filter
|
||||
import com.keylesspalace.tusky.entity.Status
|
||||
import com.keylesspalace.tusky.entity.StreamEvent
|
||||
import com.keylesspalace.tusky.fragment.TimelineFragment
|
||||
import com.keylesspalace.tusky.json.SpannedTypeAdapter
|
||||
import okhttp3.Response
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
|
||||
class TimelineStreamingListener(private val eventHub: EventHub,
|
||||
private val kind: TimelineFragment.Kind) : WebSocketListener() {
|
||||
|
||||
private val gson = buildGson()
|
||||
private var isFirstStatus = true
|
||||
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
Log.d(TAG, "Stream connected to: " + kind.name)
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||
val event = gson.fromJson(text, StreamEvent::class.java)
|
||||
val payload = event.payload
|
||||
when (event.event) {
|
||||
StreamEvent.EventType.UPDATE -> {
|
||||
val status = gson.fromJson(payload, Status::class.java)
|
||||
eventHub.dispatch(StreamUpdateEvent(status, kind, isFirstStatus))
|
||||
if (isFirstStatus) {
|
||||
isFirstStatus = false
|
||||
}
|
||||
}
|
||||
StreamEvent.EventType.DELETE -> eventHub.dispatch(StatusDeletedEvent(payload))
|
||||
StreamEvent.EventType.FILTERS_CHANGED -> eventHub.dispatch(PreferenceChangedEvent(Filter.HOME)) // It may be not a home but it doesn't matter
|
||||
else -> Log.d(TAG, "Unsupported event type.")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
||||
Log.d(TAG, "Stream closed for: " + kind.name)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "StreamingListener"
|
||||
|
||||
private fun buildGson(): Gson {
|
||||
return GsonBuilder()
|
||||
.registerTypeAdapter(Spanned::class.java, SpannedTypeAdapter())
|
||||
.create()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,11 @@
|
|||
android:icon="@drawable/ic_reject_24dp"
|
||||
android:title="@string/action_tab_reset" />
|
||||
|
||||
<item android:id="@+id/tabToggleStreaming"
|
||||
android:icon="@drawable/ic_check_24dp"
|
||||
android:title="@string/action_tab_toggle_streaming"
|
||||
android:visible="false" />
|
||||
|
||||
<item android:id="@+id/tabToggleNotificationsFilter"
|
||||
android:icon="@drawable/ic_notifications_24dp"
|
||||
android:title="@string/action_tab_toggle_notifications_filter"
|
||||
|
|
|
@ -144,6 +144,7 @@
|
|||
<string name="action_authorize">Authorize Now!</string>
|
||||
<string name="action_tab_jump_to_top">Jump to top</string>
|
||||
<string name="action_tab_reset">Reset tab</string>
|
||||
<string name="action_tab_toggle_streaming">Use streaming in this tab</string>
|
||||
<string name="action_tab_toggle_notifications_filter">Toggle notifications filter</string>
|
||||
|
||||
<string name="title_hashtags_dialog">Hashtags</string>
|
||||
|
|
|
@ -148,11 +148,6 @@
|
|||
android:title="@string/pref_title_experimental_viewpager_offscreen"
|
||||
app:singleLineTitle="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="useHTLStream"
|
||||
android:title="@string/pref_title_experimental_htl_streaming"
|
||||
app:singleLineTitle="false" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
|
Loading…
Reference in New Issue