From 372f373601ddb0abdc81f732bd6d2b58e67a4dfa Mon Sep 17 00:00:00 2001 From: Alex Ning Date: Tue, 6 Jul 2021 20:38:58 +0800 Subject: Show live comments in RPAN broadcasts. --- .../infinityforreddit/RPANComment.java | 15 ++++ .../infinityforreddit/activities/RPANActivity.java | 22 ++++- .../RPANCommentStreamRecyclerViewAdapter.java | 83 ++++++++++++++++++ .../fragments/ViewRPANBroadcastFragment.java | 98 +++++++++++++++++++++- .../infinityforreddit/utils/JSONUtils.java | 2 + 5 files changed, 215 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/RPANComment.java create mode 100644 app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RPANCommentStreamRecyclerViewAdapter.java (limited to 'app/src/main/java/ml') diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/RPANComment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/RPANComment.java new file mode 100644 index 00000000..077031bf --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/RPANComment.java @@ -0,0 +1,15 @@ +package ml.docilealligator.infinityforreddit; + +public class RPANComment { + public String author; + public String authorIconImage; + public String content; + public long createdUTC; + + public RPANComment(String author, String authorIconImage, String content, long createdUTC) { + this.author = author; + this.authorIconImage = authorIconImage; + this.content = content; + this.createdUTC = createdUTC; + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/RPANActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/RPANActivity.java index 57b419ee..c22d481f 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/RPANActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/RPANActivity.java @@ -18,6 +18,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; +import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager2.adapter.FragmentStateAdapter; import androidx.viewpager2.widget.ViewPager2; @@ -28,6 +29,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.concurrent.Executor; @@ -217,7 +219,25 @@ public class RPANActivity extends AppCompatActivity { sectionsPagerAdapter = new SectionsPagerAdapter(this); viewPager2.setAdapter(sectionsPagerAdapter); viewPager2.setOffscreenPageLimit(3); - //fixViewPager2Sensitivity(viewPager2); + fixViewPager2Sensitivity(viewPager2); + } + + private void fixViewPager2Sensitivity(ViewPager2 viewPager2) { + try { + Field recyclerViewField = ViewPager2.class.getDeclaredField("mRecyclerView"); + recyclerViewField.setAccessible(true); + + RecyclerView recyclerView = (RecyclerView) recyclerViewField.get(viewPager2); + + Field touchSlopField = RecyclerView.class.getDeclaredField("mTouchSlop"); + touchSlopField.setAccessible(true); + + Object touchSlopBox = touchSlopField.get(recyclerView); + if (touchSlopBox != null) { + int touchSlop = (int) touchSlopBox; + touchSlopField.set(recyclerView, touchSlop * 4); + } + } catch (NoSuchFieldException | IllegalAccessException ignore) {} } private class SectionsPagerAdapter extends FragmentStateAdapter { diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RPANCommentStreamRecyclerViewAdapter.java b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RPANCommentStreamRecyclerViewAdapter.java new file mode 100644 index 00000000..ba7dcd34 --- /dev/null +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/adapters/RPANCommentStreamRecyclerViewAdapter.java @@ -0,0 +1,83 @@ +package ml.docilealligator.infinityforreddit.adapters; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.request.RequestOptions; + +import java.util.ArrayList; + +import jp.wasabeef.glide.transformations.RoundedCornersTransformation; +import ml.docilealligator.infinityforreddit.R; +import ml.docilealligator.infinityforreddit.RPANComment; + +public class RPANCommentStreamRecyclerViewAdapter extends RecyclerView.Adapter { + private RequestManager glide; + private ArrayList rpanComments; + + public RPANCommentStreamRecyclerViewAdapter(Context context) { + glide = Glide.with(context); + rpanComments = new ArrayList<>(); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new RPANCommentViewHolder(LayoutInflater.from(parent.getContext()).inflate( + R.layout.item_rpan_comment, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (holder instanceof RPANCommentViewHolder) { + ((RPANCommentViewHolder) holder).authorTextView.setText(rpanComments.get(position).author); + ((RPANCommentViewHolder) holder).contentTextView.setText(rpanComments.get(position).content); + glide.load(rpanComments.get(position).authorIconImage) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0))) + .error(glide.load(R.drawable.subreddit_default_icon) + .apply(RequestOptions.bitmapTransform(new RoundedCornersTransformation(72, 0)))) + .into(((RPANCommentViewHolder) holder).iconImageView); + } + } + + @Override + public int getItemCount() { + return rpanComments == null ? 0 : rpanComments.size(); + } + + @Override + public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { + super.onViewRecycled(holder); + if (holder instanceof RPANCommentViewHolder) { + glide.clear(((RPANCommentViewHolder) holder).iconImageView); + } + } + + public void addRPANComment(RPANComment rpanComment) { + rpanComments.add(rpanComment); + notifyItemInserted(rpanComments.size() - 1); + } + + class RPANCommentViewHolder extends RecyclerView.ViewHolder { + ImageView iconImageView; + TextView authorTextView; + TextView timeTextView; + TextView contentTextView; + + public RPANCommentViewHolder(@NonNull View itemView) { + super(itemView); + iconImageView = itemView.findViewById(R.id.icon_image_view_item_rpan_comment); + authorTextView = itemView.findViewById(R.id.author_text_view_item_rpan_comment); + contentTextView = itemView.findViewById(R.id.content_text_view_item_rpan_comment); + } + } +} diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java index 80f98cee..4eed627c 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRPANBroadcastFragment.java @@ -2,8 +2,11 @@ package ml.docilealligator.infinityforreddit.fragments; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,7 +14,9 @@ import android.widget.ImageButton; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.RecyclerView; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayerFactory; @@ -28,9 +33,14 @@ import com.google.android.exoplayer2.ui.PlayerView; import com.google.android.exoplayer2.ui.TrackSelectionDialogBuilder; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; -import com.google.android.exoplayer2.upstream.cache.SimpleCache; import com.google.android.exoplayer2.util.Util; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + import javax.inject.Inject; import javax.inject.Named; @@ -39,19 +49,30 @@ import butterknife.ButterKnife; import ml.docilealligator.infinityforreddit.Infinity; import ml.docilealligator.infinityforreddit.R; import ml.docilealligator.infinityforreddit.RPANBroadcast; +import ml.docilealligator.infinityforreddit.RPANComment; +import ml.docilealligator.infinityforreddit.adapters.RPANCommentStreamRecyclerViewAdapter; import ml.docilealligator.infinityforreddit.customtheme.CustomThemeWrapper; +import ml.docilealligator.infinityforreddit.utils.JSONUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.WebSocket; +import okhttp3.WebSocketListener; public class ViewRPANBroadcastFragment extends Fragment { public static final String EXTRA_RPAN_BROADCAST = "ERB"; private static final String IS_MUTE_STATE = "IMS"; + @BindView(R.id.constraint_layout_exo_rpan_broadcast_playback_control_view) + ConstraintLayout constraintLayout; @BindView(R.id.player_view_view_rpan_broadcast_fragment) PlayerView playerView; - @BindView(R.id.mute_exo_playback_control_view) + @BindView(R.id.recycler_view_exo_rpan_broadcast_playback_control_view) + RecyclerView recyclerView; + @BindView(R.id.mute_exo_rpan_broadcast_playback_control_view) ImageButton muteButton; - @BindView(R.id.hd_exo_playback_control_view) + @BindView(R.id.hd_exo_rpan_broadcast_playback_control_view) ImageButton hdButton; @Inject @Named("default") @@ -62,12 +83,14 @@ public class ViewRPANBroadcastFragment extends Fragment { @Inject CustomThemeWrapper mCustomThemeWrapper; @Inject - SimpleCache mSimpleCache; + Executor mExecutor; private AppCompatActivity mActivity; private RPANBroadcast rpanBroadcast; private SimpleExoPlayer player; private DefaultTrackSelector trackSelector; private DataSource.Factory dataSourceFactory; + private Handler handler; + private RPANCommentStreamRecyclerViewAdapter adapter; private boolean wasPlaying; private boolean isMute = false; @@ -90,6 +113,20 @@ public class ViewRPANBroadcastFragment extends Fragment { rpanBroadcast = getArguments().getParcelable(EXTRA_RPAN_BROADCAST); + if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT || getResources().getBoolean(R.bool.isTablet)) { + //Set player controller bottom margin in order to display it above the navbar + int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + //LinearLayout controllerLinearLayout = findViewById(R.id.linear_layout_exo_playback_control_view); + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) constraintLayout.getLayoutParams(); + params.bottomMargin = getResources().getDimensionPixelSize(resourceId); + } else { + //Set player controller right margin in order to display it above the navbar + int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android"); + //LinearLayout controllerLinearLayout = findViewById(R.id.linear_layout_exo_playback_control_view); + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) constraintLayout.getLayoutParams(); + params.rightMargin = getResources().getDimensionPixelSize(resourceId); + } + playerView.setControllerVisibilityListener(visibility -> { switch (visibility) { case View.GONE: @@ -186,6 +223,21 @@ public class ViewRPANBroadcastFragment extends Fragment { } }); + adapter = new RPANCommentStreamRecyclerViewAdapter(mActivity); + recyclerView.setAdapter(adapter); + + handler = new Handler(); + + Request request = new Request.Builder().url(rpanBroadcast.rpanPost.liveCommentsWebsocketUrl).build(); + CommentStreamWebSocketListener listener = new CommentStreamWebSocketListener(this::parseComment); + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .writeTimeout(30, TimeUnit.SECONDS) + .build(); + WebSocket webSocket = okHttpClient.newWebSocket(request, listener); + okHttpClient.dispatcher().executorService().shutdown(); + return rootView; } @@ -203,6 +255,26 @@ public class ViewRPANBroadcastFragment extends Fragment { return false; } + private void parseComment(String commentJson) { + mExecutor.execute(() -> { + try { + JSONObject commentObject = new JSONObject(commentJson); + if (commentObject.getString(JSONUtils.TYPE_KEY).equals("new_comment")) { + JSONObject payload = commentObject.getJSONObject(JSONUtils.PAYLOAD_KEY); + RPANComment rpanComment = new RPANComment( + payload.getString(JSONUtils.AUTHOR_KEY), + payload.getString(JSONUtils.AUTHOR_ICON_IMAGE), + payload.getString(JSONUtils.BODY_KEY), + payload.getLong(JSONUtils.CREATED_UTC_KEY)); + + handler.post(() -> adapter.addRPANComment(rpanComment)); + } + } catch (JSONException e) { + e.printStackTrace(); + } + }); + } + @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); @@ -237,4 +309,22 @@ public class ViewRPANBroadcastFragment extends Fragment { super.onAttach(context); mActivity = (AppCompatActivity) context; } + + private static class CommentStreamWebSocketListener extends WebSocketListener { + MessageReceivedListener messageReceivedListener; + + CommentStreamWebSocketListener(MessageReceivedListener messageReceivedListener) { + this.messageReceivedListener = messageReceivedListener; + } + + @Override + public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) { + Log.i("asfasdf", "s " + text); + messageReceivedListener.onMessage(text); + } + + interface MessageReceivedListener { + void onMessage(String text); + } + } } \ No newline at end of file diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java index 579242a3..027d69dd 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/JSONUtils.java @@ -162,4 +162,6 @@ public class JSONUtils { public static final String CHAT_DISABLED_KEY = "chat_disabled"; public static final String BROADCAST_TIME_KEY = "broadcast_time"; public static final String ESTIMATED_REMAINING_TIME_KEY = "estimated_remaining_time"; + public static final String PAYLOAD_KEY = "payload"; + public static final String AUTHOR_ICON_IMAGE = "author_icon_img"; } -- cgit v1.2.3