diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-22 17:52:22 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-22 17:52:22 +0200 |
| commit | 9fba091bb394e8e4c7e8ccd0702081bd1a939047 (patch) | |
| tree | d21c451a94452d2b36dfccca35b1b45adfc2bc59 /app/src/main/java/mygram/droid | |
| parent | 899b1d9812c61a33554b67e3026850684e686ce1 (diff) | |
| download | mygram-droid-9fba091bb394e8e4c7e8ccd0702081bd1a939047.tar.gz mygram-droid-9fba091bb394e8e4c7e8ccd0702081bd1a939047.zip | |
Folder pictures, photo/video tile marks, folder in the viewer
Home rows and the folder header show the folder's round picture next to
its name. Post tiles carry a photo or video mark in the top left, on a
dark dot so it reads on bright thumbnails. The viewer shows the current
post's folder picture and name in the top left; pictures come from the
last loaded folder list, so before one loads only the name shows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'app/src/main/java/mygram/droid')
| -rw-r--r-- | app/src/main/java/mygram/droid/Api.java | 7 | ||||
| -rw-r--r-- | app/src/main/java/mygram/droid/FolderActivity.java | 4 | ||||
| -rw-r--r-- | app/src/main/java/mygram/droid/HomeRows.java | 19 | ||||
| -rw-r--r-- | app/src/main/java/mygram/droid/TileAdapter.java | 28 | ||||
| -rw-r--r-- | app/src/main/java/mygram/droid/Ui.java | 27 | ||||
| -rw-r--r-- | app/src/main/java/mygram/droid/ViewerActivity.java | 20 |
6 files changed, 94 insertions, 11 deletions
diff --git a/app/src/main/java/mygram/droid/Api.java b/app/src/main/java/mygram/droid/Api.java index f603b1b..c7d08eb 100644 --- a/app/src/main/java/mygram/droid/Api.java +++ b/app/src/main/java/mygram/droid/Api.java @@ -35,6 +35,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -46,6 +47,9 @@ final class Api { static final ExecutorService IO = Executors.newFixedThreadPool(4); private static final int TIMEOUT_MS = 15000; private static final int PAGE = 60; + /** Folder slug to picture URL, filled by folders(); the viewer reads it. */ + // ponytail: empty until a folder list has loaded once, so the viewer then shows the name alone. + static final Map<String, String> AVATARS = new ConcurrentHashMap<>(); private Api() { } @@ -161,6 +165,9 @@ final class Api { List<Folder> out = new ArrayList<>(); for (int i = 0; i < items.length(); i++) out.add(new Folder(items.getJSONObject(i), Prefs.server())); + for (Folder f : out) + if (f.avatarUrl != null) + AVATARS.put(f.slug, f.avatarUrl); out.sort((a, b) -> Long.compare(b.latest, a.latest)); return out; } diff --git a/app/src/main/java/mygram/droid/FolderActivity.java b/app/src/main/java/mygram/droid/FolderActivity.java index 65ac596..84e7125 100644 --- a/app/src/main/java/mygram/droid/FolderActivity.java +++ b/app/src/main/java/mygram/droid/FolderActivity.java @@ -42,7 +42,7 @@ public final class FolderActivity extends Activity { static void open(Context c, Api.Folder f) { c.startActivity(new Intent(c, FolderActivity.class).putExtra("slug", f.slug).putExtra("name", f.name) - .putExtra("count", f.count)); + .putExtra("count", f.count).putExtra("avatar", f.avatarUrl)); } @Override @@ -50,7 +50,7 @@ public final class FolderActivity extends Activity { super.onCreate(state); LinearLayout column = new LinearLayout(this); column.setOrientation(LinearLayout.VERTICAL); - column.addView(Ui.header(this, getIntent().getStringExtra("name"))); + column.addView(Ui.header(this, getIntent().getStringExtra("name"), getIntent().getStringExtra("avatar"))); content = new FrameLayout(this); column.addView(content, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1)); setContentView(column); diff --git a/app/src/main/java/mygram/droid/HomeRows.java b/app/src/main/java/mygram/droid/HomeRows.java index 6105c1c..9615f6f 100644 --- a/app/src/main/java/mygram/droid/HomeRows.java +++ b/app/src/main/java/mygram/droid/HomeRows.java @@ -17,7 +17,9 @@ package mygram.droid; import android.app.Activity; import android.content.Context; +import android.view.Gravity; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -34,12 +36,14 @@ final class HomeRows extends RecyclerView.Adapter<HomeRows.Row> { static final class Row extends RecyclerView.ViewHolder { final TextView title; + final ImageView avatar; final RecyclerView tiles; /** Backing list of the row's one adapter: rebinds refill it instead of replacing the adapter. */ final List<TileAdapter.Tile> list = new ArrayList<>(); - Row(LinearLayout v, TextView title, RecyclerView tiles) { + Row(LinearLayout v, ImageView avatar, TextView title, RecyclerView tiles) { super(v); + this.avatar = avatar; this.title = title; this.tiles = tiles; tiles.setAdapter(new TileAdapter(list, Ui.POST_W, Ui.POST_H)); @@ -62,19 +66,26 @@ final class HomeRows extends RecyclerView.Adapter<HomeRows.Row> { v.setOrientation(LinearLayout.VERTICAL); v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + LinearLayout head = new LinearLayout(c); + head.setGravity(Gravity.CENTER_VERTICAL); + head.setPadding(Ui.dp(c, 36), Ui.dp(c, 16), 0, 0); + ImageView avatar = Ui.avatar(c, 32); + ((LinearLayout.LayoutParams) avatar.getLayoutParams()).rightMargin = Ui.dp(c, 12); + head.addView(avatar); TextView title = Ui.text(c, "", 18); - title.setPadding(Ui.dp(c, 36), Ui.dp(c, 16), 0, 0); - v.addView(title); + head.addView(title); + v.addView(head); RecyclerView tiles = Ui.list(c, RecyclerView.HORIZONTAL); v.addView(tiles, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, Ui.dp(c, Ui.POST_H + 24))); - return new Row(v, title, tiles); + return new Row(v, avatar, title, tiles); } @Override public void onBindViewHolder(Row row, int position) { Api.Folder f = folders.get(position); row.title.setText(f.name); + Ui.setAvatar(row.avatar, f.avatarUrl); List<Post> posts = samples.get(f.slug); if (posts == null) { posts = List.of(); diff --git a/app/src/main/java/mygram/droid/TileAdapter.java b/app/src/main/java/mygram/droid/TileAdapter.java index cf80f51..5e53109 100644 --- a/app/src/main/java/mygram/droid/TileAdapter.java +++ b/app/src/main/java/mygram/droid/TileAdapter.java @@ -16,6 +16,7 @@ package mygram.droid; import android.content.Context; import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; @@ -35,6 +36,8 @@ final class TileAdapter extends RecyclerView.Adapter<TileAdapter.Holder> { final String image, label; final int icon, postId; final Runnable click; + /** Photo or video mark in the top left; 0 for none. */ + int badge; /** image or icon is shown; postId is -1 for tiles that are not posts. */ Tile(String image, String label, int icon, int postId, Runnable click) { @@ -47,18 +50,21 @@ final class TileAdapter extends RecyclerView.Adapter<TileAdapter.Holder> { } static Tile post(Post p, Runnable click) { - return new Tile(p.media.get(0).thumbnailUrl, null, 0, p.id, click); + Tile t = new Tile(p.media.get(0).thumbnailUrl, null, 0, p.id, click); + t.badge = p.media.get(0).video ? R.drawable.ic_videocam : R.drawable.ic_photo; + return t; } static final class Holder extends RecyclerView.ViewHolder { - final ImageView image, heart; + final ImageView image, heart, badge; final TextView label; - Holder(View v, ImageView image, TextView label, ImageView heart) { + Holder(View v, ImageView image, TextView label, ImageView heart, ImageView badge) { super(v); this.image = image; this.label = label; this.heart = heart; + this.badge = badge; } } @@ -97,8 +103,19 @@ final class TileAdapter extends RecyclerView.Adapter<TileAdapter.Holder> { FrameLayout.LayoutParams hp = new FrameLayout.LayoutParams(s, s, Gravity.TOP | Gravity.END); hp.setMargins(p, p, p, p); frame.addView(heart, hp); + ImageView badge = new ImageView(c); + GradientDrawable dot = new GradientDrawable(); + dot.setShape(GradientDrawable.OVAL); + dot.setColor(0x80000000); // readable on bright thumbnails + badge.setBackground(dot); + int bp = Ui.dp(c, 4); + badge.setPadding(bp, bp, bp, bp); + int bs = Ui.dp(c, 26); + FrameLayout.LayoutParams bl = new FrameLayout.LayoutParams(bs, bs, Gravity.TOP | Gravity.START); + bl.setMargins(p, p, p, p); + frame.addView(badge, bl); Ui.focusable(frame); - return new Holder(frame, image, label, heart); + return new Holder(frame, image, label, heart, badge); } @Override @@ -116,6 +133,9 @@ final class TileAdapter extends RecyclerView.Adapter<TileAdapter.Holder> { } h.label.setText(t.label); h.label.setVisibility(t.label == null ? View.GONE : View.VISIBLE); + h.badge.setVisibility(t.badge == 0 ? View.GONE : View.VISIBLE); + h.badge.setImageResource(t.badge); + h.badge.setContentDescription(t.badge == R.drawable.ic_videocam ? "Video" : "Photo"); h.heart.setVisibility(t.postId >= 0 && Likes.has(t.postId) ? View.VISIBLE : View.GONE); h.itemView.setContentDescription(t.label != null ? t.label : "Post"); h.itemView.setOnClickListener(v -> t.click.run()); diff --git a/app/src/main/java/mygram/droid/Ui.java b/app/src/main/java/mygram/droid/Ui.java index 172fbff..6667d33 100644 --- a/app/src/main/java/mygram/droid/Ui.java +++ b/app/src/main/java/mygram/droid/Ui.java @@ -33,6 +33,8 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.bumptech.glide.Glide; + import java.util.ArrayList; import java.util.List; @@ -156,17 +158,42 @@ final class Ui { /** Back arrow and title, for screens outside the main view. */ static LinearLayout header(Activity a, String title) { + return header(a, title, null); + } + + /** Back arrow, the folder picture when there is one, and title. */ + static LinearLayout header(Activity a, String title, String avatarUrl) { LinearLayout header = new LinearLayout(a); header.setGravity(Gravity.CENTER_VERTICAL); int p = dp(a, 16); header.setPadding(p * 2, p, p * 2, 0); header.addView(iconButton(a, R.drawable.ic_arrow_back, "Back", v -> a.finish())); + ImageView avatar = avatar(a, 40); + ((LinearLayout.LayoutParams) avatar.getLayoutParams()).leftMargin = p; + header.addView(avatar); + setAvatar(avatar, avatarUrl); TextView t = text(a, title, 24); t.setPadding(p, 0, 0, 0); header.addView(t); return header; } + /** A round folder picture of size dp, for a horizontal LinearLayout. */ + static ImageView avatar(Context c, int size) { + ImageView v = new ImageView(c); + v.setLayoutParams(new LinearLayout.LayoutParams(dp(c, size), dp(c, size))); + return v; + } + + /** Loads url round, or hides the view when the folder has no picture. */ + static void setAvatar(ImageView v, String url) { + v.setVisibility(url == null ? View.GONE : View.VISIBLE); + if (url == null) + Glide.with(v).clear(v); + else + Glide.with(v).load(url).circleCrop().into(v); + } + static void blur(ImageView v, float radius) { v.setRenderEffect(RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP)); } diff --git a/app/src/main/java/mygram/droid/ViewerActivity.java b/app/src/main/java/mygram/droid/ViewerActivity.java index 4845cbd..369db5d 100644 --- a/app/src/main/java/mygram/droid/ViewerActivity.java +++ b/app/src/main/java/mygram/droid/ViewerActivity.java @@ -29,6 +29,7 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; @@ -73,7 +74,8 @@ public final class ViewerActivity extends Activity { private PlayerView video; private ImageView backdrop, photo, pauseIcon, heart, bigHeart; private ProgressBar progress; - private TextView message; + private TextView message, folderName; + private ImageView folderAvatar; private int index, slide; /** Bumped on every slide change; late callbacks for older slides are dropped. */ private volatile int generation; @@ -127,6 +129,19 @@ public final class ViewerActivity extends Activity { bigHeart = overlay(root, R.drawable.ic_favorite, "Liked", 160, Gravity.CENTER); heart.setColorFilter(Ui.RED); bigHeart.setColorFilter(Ui.RED); + LinearLayout folder = new LinearLayout(this); + folder.setGravity(Gravity.CENTER_VERTICAL); + folderAvatar = Ui.avatar(this, 40); + ((LinearLayout.LayoutParams) folderAvatar.getLayoutParams()).rightMargin = Ui.dp(this, 12); + folder.addView(folderAvatar); + folderName = Ui.text(this, "", 20); + folderName.setShadowLayer(6, 0, 0, Color.BLACK); + folder.addView(folderName); + int m = Ui.dp(this, 24); + FrameLayout.LayoutParams fp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.TOP | Gravity.START); + fp.setMargins(m, m, m, m); + root.addView(folder, fp); setContentView(root); if (!Playlist.posts.isEmpty()) { @@ -162,6 +177,8 @@ public final class ViewerActivity extends Activity { private void show(int i) { index = i; slide = 0; + folderName.setText(post().folderName); + Ui.setAvatar(folderAvatar, Api.AVATARS.get(post().folderSlug)); maybeLoadMore(); showSlide(); } @@ -432,6 +449,7 @@ public final class ViewerActivity extends Activity { generation++; Glide.with(photo).clear(photo); Glide.with(backdrop).clear(backdrop); + Glide.with(folderAvatar).clear(folderAvatar); player.release(); } } |
