diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 272b993c..46fb5d23 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -30,6 +30,7 @@
+
\ No newline at end of file
diff --git a/app/src/main/java/com/keylesspalace/tusky/Status.java b/app/src/main/java/com/keylesspalace/tusky/Status.java
index d69855a3..1b5100cd 100644
--- a/app/src/main/java/com/keylesspalace/tusky/Status.java
+++ b/app/src/main/java/com/keylesspalace/tusky/Status.java
@@ -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;
+ }
+ }
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java b/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java
index fb261bb1..b43883e0 100644
--- a/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java
+++ b/app/src/main/java/com/keylesspalace/tusky/StatusActionListener.java
@@ -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);
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java b/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java
index 41a83169..388021bb 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java
+++ b/app/src/main/java/com/keylesspalace/tusky/TimelineAdapter.java
@@ -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
diff --git a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java
index f687e99a..60953151 100644
--- a/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java
+++ b/app/src/main/java/com/keylesspalace/tusky/TimelineFragment.java
@@ -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;
+ }
+ }
+ }
}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java b/app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java
new file mode 100644
index 00000000..f799ca80
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewMediaFragment.java
@@ -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();
+ }
+}
diff --git a/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java b/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java
new file mode 100644
index 00000000..4b64bcc1
--- /dev/null
+++ b/app/src/main/java/com/keylesspalace/tusky/ViewVideoActivity.java
@@ -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();
+ }
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 3ff98df8..a65e5f3b 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,5 +1,5 @@
-
+ tools:context="com.keylesspalace.tusky.MainActivity">
-
+ android:layout_height="match_parent"
+ android:orientation="vertical">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_view_video.xml b/app/src/main/res/layout/activity_view_video.xml
new file mode 100644
index 00000000..d904f05e
--- /dev/null
+++ b/app/src/main/res/layout/activity_view_video.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_view_media.xml b/app/src/main/res/layout/fragment_view_media.xml
new file mode 100644
index 00000000..50de8b59
--- /dev/null
+++ b/app/src/main/res/layout/fragment_view_media.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_status.xml b/app/src/main/res/layout/item_status.xml
index a20f4a4a..b9f42f21 100644
--- a/app/src/main/res/layout/item_status.xml
+++ b/app/src/main/res/layout/item_status.xml
@@ -76,11 +76,62 @@
android:layout_toEndOf="@+id/status_avatar"
android:layout_below="@+id/status_name_bar" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 5f1f7994..eeda0aca 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -4,4 +4,5 @@
#303F9F
#FF4081
#4F4F4F
+ #000000
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index 4f478227..b1cfad05 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -5,4 +5,5 @@
4dp
8dp
5dp
+ 4dp