added media previews to timeline and media viewers

This commit is contained in:
Vavassor 2017-01-10 01:14:27 -05:00
parent acbd5acb20
commit e551de7521
13 changed files with 328 additions and 37 deletions

View file

@ -30,6 +30,7 @@
<activity
android:name=".ComposeActivity"
android:windowSoftInputMode="stateVisible|adjustResize" />
<activity android:name=".ViewVideoActivity" />
</application>
</manifest>

View file

@ -1,9 +1,13 @@
package com.keylesspalace.tusky;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.provider.MediaStore;
import android.text.Html;
import android.text.Spanned;
import com.android.volley.toolbox.NetworkImageView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -38,6 +42,9 @@ public class Status {
/** whether the authenticated user has favourited this status */
private boolean favourited;
private Visibility visibility;
private MediaAttachment[] attachments = null;
public static final int MAX_MEDIA_ATTACHMENTS = 4;
public Status(String id, String accountId, String displayName, String username, Spanned content,
String avatar, Date createdAt, boolean reblogged, boolean favourited,
@ -52,6 +59,7 @@ public class Status {
this.reblogged = reblogged;
this.favourited = favourited;
this.visibility = Visibility.valueOf(visibility.toUpperCase());
this.attachments = new MediaAttachment[0];
}
public String getId() {
@ -98,6 +106,10 @@ public class Status {
return visibility;
}
public MediaAttachment[] getAttachments() {
return attachments;
}
public void setRebloggedByUsername(String name) {
rebloggedByUsername = name;
}
@ -110,6 +122,10 @@ public class Status {
this.favourited = favourited;
}
public void setAttachments(MediaAttachment[] attachments) {
this.attachments = attachments;
}
@Override
public int hashCode() {
return id.hashCode();
@ -173,6 +189,21 @@ public class Status {
String username = account.getString("acct");
String avatar = account.getString("avatar");
JSONArray mediaAttachments = object.getJSONArray("media_attachments");
MediaAttachment[] attachments = null;
if (mediaAttachments != null) {
int n = mediaAttachments.length();
attachments = new MediaAttachment[n];
for (int i = 0; i < n; i++) {
JSONObject attachment = mediaAttachments.getJSONObject(i);
String url = attachment.getString("url");
String previewUrl = attachment.getString("preview_url");
String type = attachment.getString("type");
attachments[i] = new MediaAttachment(url, previewUrl,
MediaAttachment.Type.valueOf(type.toUpperCase()));
}
}
Status reblog = null;
/* This case shouldn't be hit after the first recursion at all. But if this method is
* passed unusual data this check will prevent extra recursion */
@ -193,6 +224,9 @@ public class Status {
id, accountId, displayName, username, contentPlus, avatar, createdAt,
reblogged, favourited, visibility);
}
if (attachments != null) {
status.setAttachments(attachments);
}
return status;
}
@ -204,4 +238,33 @@ public class Status {
}
return statuses;
}
public static class MediaAttachment {
enum Type {
IMAGE,
VIDEO,
}
private String url;
private String previewUrl;
private Type type;
public MediaAttachment(String url, String previewUrl, Type type) {
this.url = url;
this.previewUrl = previewUrl;
this.type = type;
}
public String getUrl() {
return url;
}
public String getPreviewUrl() {
return previewUrl;
}
public Type getType() {
return type;
}
}
}

View file

@ -6,4 +6,5 @@ public interface StatusActionListener {
void onReblog(final boolean reblog, final int position);
void onFavourite(final boolean favourite, final int position);
void onMore(View view, final int position);
void onViewMedia(String url, Status.MediaAttachment.Type type);
}

View file

@ -2,8 +2,10 @@ package com.keylesspalace.tusky;
import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v7.widget.PagerSnapHelper;
import android.support.v7.widget.RecyclerView;
import android.text.Spanned;
import android.text.style.ImageSpan;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -53,6 +55,7 @@ public class TimelineAdapter extends RecyclerView.Adapter {
} else {
holder.setRebloggedByUsername(rebloggedByUsername);
}
holder.setMediaPreviews(status.getAttachments(), listener);
holder.setupButtons(listener, position);
if (status.getVisibility() == Status.Visibility.PRIVATE) {
holder.disableReblogging();
@ -112,6 +115,11 @@ public class TimelineAdapter extends RecyclerView.Adapter {
private ImageButton moreButton;
private boolean favourited;
private boolean reblogged;
private NetworkImageView mediaPreview0;
private NetworkImageView mediaPreview1;
private NetworkImageView mediaPreview2;
private NetworkImageView mediaPreview3;
private String[] mediaAttachmentUrls;
public ViewHolder(View itemView) {
super(itemView);
@ -128,6 +136,10 @@ public class TimelineAdapter extends RecyclerView.Adapter {
moreButton = (ImageButton) itemView.findViewById(R.id.status_more);
reblogged = false;
favourited = false;
mediaPreview0 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_0);
mediaPreview1 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_1);
mediaPreview2 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_2);
mediaPreview3 = (NetworkImageView) itemView.findViewById(R.id.status_media_preview_3);
}
public void setDisplayName(String name) {
@ -234,6 +246,37 @@ public class TimelineAdapter extends RecyclerView.Adapter {
}
}
public void setMediaPreviews(final Status.MediaAttachment[] attachments,
final StatusActionListener listener) {
final NetworkImageView[] previews = {
mediaPreview0,
mediaPreview1,
mediaPreview2,
mediaPreview3
};
Context context = mediaPreview0.getContext();
ImageLoader imageLoader = VolleySingleton.getInstance(context).getImageLoader();
int n = Math.min(attachments.length, Status.MAX_MEDIA_ATTACHMENTS);
for (int i = 0; i < n; i++) {
String previewUrl = attachments[i].getPreviewUrl();
previews[i].setImageUrl(previewUrl, imageLoader);
previews[i].setVisibility(View.VISIBLE);
final String url = attachments[i].getUrl();
final Status.MediaAttachment.Type type = attachments[i].getType();
previews[i].setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onViewMedia(url, type);
}
});
}
// Hide any of the placeholder previews beyond the ones set.
for (int i = n; i < Status.MAX_MEDIA_ATTACHMENTS; i++) {
previews[i].setImageUrl(null, imageLoader);
previews[i].setVisibility(View.GONE);
}
}
public void setupButtons(final StatusActionListener listener, final int position) {
reblogButton.setOnClickListener(new View.OnClickListener() {
@Override

View file

@ -1,11 +1,13 @@
package com.keylesspalace.tusky;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.DividerItemDecoration;
@ -184,7 +186,8 @@ public class TimelineFragment extends Fragment implements
}
public void onFetchTimelineFailure(Exception exception) {
Toast.makeText(getContext(), R.string.error_fetching_timeline, Toast.LENGTH_SHORT).show();
Toast.makeText(getContext(), R.string.error_fetching_timeline, Toast.LENGTH_SHORT)
.show();
swipeRefreshLayout.setRefreshing(false);
}
@ -312,4 +315,24 @@ public class TimelineFragment extends Fragment implements
});
popup.show();
}
public void onViewMedia(String url, Status.MediaAttachment.Type type) {
switch (type) {
case IMAGE: {
Fragment newFragment = ViewMediaFragment.newInstance(url);
FragmentManager manager = getFragmentManager();
manager.beginTransaction()
.add(R.id.overlay_fragment_container, newFragment)
.addToBackStack(null)
.commit();
break;
}
case VIDEO: {
Intent intent = new Intent(getContext(), ViewVideoActivity.class);
intent.putExtra("url", url);
startActivity(intent);
break;
}
}
}
}

View file

@ -0,0 +1,45 @@
package com.keylesspalace.tusky;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.NetworkImageView;
public class ViewMediaFragment extends Fragment {
public static ViewMediaFragment newInstance(String url) {
Bundle arguments = new Bundle();
ViewMediaFragment fragment = new ViewMediaFragment();
arguments.putString("url", url);
fragment.setArguments(arguments);
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_view_media, container, false);
Bundle arguments = getArguments();
String url = arguments.getString("url");
NetworkImageView image = (NetworkImageView) rootView.findViewById(R.id.view_media_image);
ImageLoader imageLoader = VolleySingleton.getInstance(getContext()).getImageLoader();
image.setImageUrl(url, imageLoader);
rootView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
dismiss();
}
});
return rootView;
}
private void dismiss() {
getFragmentManager().popBackStack();
}
}

View file

@ -0,0 +1,21 @@
package com.keylesspalace.tusky;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.MediaController;
import android.widget.VideoView;
public class ViewVideoActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view_video);
String url = getIntent().getStringExtra("url");
VideoView videoView = (VideoView) findViewById(R.id.video_player);
videoView.setVideoPath(url);
MediaController controller = new MediaController(this);
videoView.setMediaController(controller);
controller.show();
videoView.start();
}
}

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_main"
@ -9,7 +9,11 @@
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.keylesspalace.tusky.MainActivity"
tools:context="com.keylesspalace.tusky.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
@ -50,3 +54,12 @@
</android.support.v4.view.ViewPager>
</LinearLayout>
<FrameLayout
android:id="@+id/overlay_fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</RelativeLayout>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/view_video_background">
<VideoView
android:id="@+id/video_player"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
</RelativeLayout>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#60000000">
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/view_media_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
android:scaleType="fitCenter" />
</RelativeLayout>

View file

@ -76,11 +76,62 @@
android:layout_toEndOf="@+id/status_avatar"
android:layout_below="@+id/status_name_bar" />
<LinearLayout
android:id="@+id/status_media_preview_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_below="@+id/status_content"
android:layout_toRightOf="@+id/status_avatar">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/status_media_preview_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:scaleType="centerCrop"
android:layout_marginTop="@dimen/status_media_preview_top_margin" />
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/status_media_preview_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:scaleType="centerCrop"
android:layout_marginTop="@dimen/status_media_preview_top_margin" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/status_media_preview_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<com.android.volley.toolbox.NetworkImageView
android:id="@+id/status_media_preview_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:scaleType="centerCrop" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/status_content"
android:layout_below="@id/status_media_preview_container"
android:layout_toRightOf="@+id/status_avatar"
android:paddingBottom="8dp"
android:paddingTop="8dp">

View file

@ -4,4 +4,5 @@
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="gray">#4F4F4F</color>
<color name="view_video_background">#000000</color>
</resources>

View file

@ -5,4 +5,5 @@
<dimen name="status_since_created_left_margin">4dp</dimen>
<dimen name="status_avatar_padding">8dp</dimen>
<dimen name="status_boost_icon_vertical_padding">5dp</dimen>
<dimen name="status_media_preview_top_margin">4dp</dimen>
</resources>