diff options
9 files changed, 358 insertions, 6 deletions
diff --git a/app/build.gradle b/app/build.gradle index b817d23f..fab2da39 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,6 +177,8 @@ dependencies { implementation 'androidx.palette:palette:1.0.0' + implementation 'com.tinder.scarlet:scarlet:0.1.12' + /**** Builds and flavors ****/ // debugImplementation because LeakCanary should only run in debug builds. 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<RecyclerView.ViewHolder> { + private RequestManager glide; + private ArrayList<RPANComment> 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"; } diff --git a/app/src/main/res/layout/exo_rpan_broadcast_playback_control_view.xml b/app/src/main/res/layout/exo_rpan_broadcast_playback_control_view.xml new file mode 100644 index 00000000..da7faea2 --- /dev/null +++ b/app/src/main/res/layout/exo_rpan_broadcast_playback_control_view.xml @@ -0,0 +1,102 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="#44000000" + android:id="@+id/constraint_layout_exo_rpan_broadcast_playback_control_view" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <LinearLayout + android:id="@+id/control_linear_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingTop="4dp" + android:orientation="horizontal" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"> + + <ImageButton + android:id="@+id/hd_exo_rpan_broadcast_playback_control_view" + android:src="@drawable/ic_video_quality_24dp" + style="@style/ExoMediaButton" + android:visibility="gone" /> + + <ImageButton android:id="@id/exo_rew" + style="@style/ExoMediaButton.Rewind" /> + + <ImageButton android:id="@id/exo_play" + style="@style/ExoMediaButton.Play"/> + + <ImageButton android:id="@id/exo_pause" + style="@style/ExoMediaButton.Pause" /> + + <ImageButton android:id="@id/exo_ffwd" + style="@style/ExoMediaButton.FastForward" /> + + <ImageButton + android:id="@+id/mute_exo_rpan_broadcast_playback_control_view" + style="@style/ExoMediaButton" + android:visibility="gone" /> + + </LinearLayout> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/recycler_view_exo_rpan_broadcast_playback_control_view" + android:layout_width="match_parent" + android:layout_height="0dp" + android:paddingTop="16dp" + android:paddingStart="16dp" + android:paddingEnd="16dp" + app:layout_constraintTop_toBottomOf="@id/control_linear_layout" + app:layout_constraintBottom_toTopOf="@id/time_bar_linear_layout" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> + + <LinearLayout + android:id="@+id/time_bar_linear_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:padding="16dp" + android:gravity="center_vertical" + android:orientation="horizontal" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"> + + <TextView android:id="@id/exo_position" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textStyle="bold" + android:paddingStart="0dp" + android:paddingEnd="4dp" + android:includeFontPadding="false" + android:textColor="#FFFFFF" + android:fontFamily="?attr/font_family" /> + + <com.google.android.exoplayer2.ui.DefaultTimeBar + android:id="@id/exo_progress" + android:layout_width="0dp" + android:layout_weight="1" + android:layout_height="26dp" + app:bar_height="2dp" + app:scrubber_color="@color/colorAccent" + app:played_color="@color/colorAccent" /> + + <TextView android:id="@id/exo_duration" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textSize="14sp" + android:textStyle="bold" + android:paddingStart="4dp" + android:paddingEnd="0dp" + android:includeFontPadding="false" + android:textColor="#FFFFFF" + android:fontFamily="?attr/font_family" /> + + </LinearLayout> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_view_rpan_broadcast.xml b/app/src/main/res/layout/fragment_view_rpan_broadcast.xml index bab40403..abab4f52 100644 --- a/app/src/main/res/layout/fragment_view_rpan_broadcast.xml +++ b/app/src/main/res/layout/fragment_view_rpan_broadcast.xml @@ -10,6 +10,6 @@ android:id="@+id/player_view_view_rpan_broadcast_fragment" android:layout_width="match_parent" android:layout_height="match_parent" - app:controller_layout_id="@layout/exo_playback_control_view" /> + app:controller_layout_id="@layout/exo_rpan_broadcast_playback_control_view" /> </FrameLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/item_rpan_comment.xml b/app/src/main/res/layout/item_rpan_comment.xml new file mode 100644 index 00000000..74766a90 --- /dev/null +++ b/app/src/main/res/layout/item_rpan_comment.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:paddingBottom="8dp"> + + <ImageView + android:id="@+id/icon_image_view_item_rpan_comment" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_gravity="center_vertical" /> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:layout_gravity="center_vertical" + android:layout_marginStart="16dp" + android:layout_marginEnd="0dp" + android:orientation="vertical"> + + <TextView + android:id="@+id/author_text_view_item_rpan_comment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textColor="@color/colorPrimary" /> + + <TextView + android:id="@+id/content_text_view_item_rpan_comment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:textColor="#FFFFFF" /> + + </LinearLayout> + +</LinearLayout>
\ No newline at end of file |