diff options
Diffstat (limited to 'app/src/main/java/mygram/droid/Ui.java')
| -rw-r--r-- | app/src/main/java/mygram/droid/Ui.java | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/app/src/main/java/mygram/droid/Ui.java b/app/src/main/java/mygram/droid/Ui.java new file mode 100644 index 0000000..852fe54 --- /dev/null +++ b/app/src/main/java/mygram/droid/Ui.java @@ -0,0 +1,155 @@ +/* + * mygram-droid: Android TV client for Foldergram. + * Copyright (C) 2026 Danilo M. <danix@danix.xyz> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package mygram.droid; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.RenderEffect; +import android.graphics.Shader; +import android.text.InputType; +import android.view.Gravity; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +/** View helpers. The UI is built in code, there are no layout files. */ +final class Ui { + static final int RED = 0xFFFF3B5C; + /** Post tiles: portrait, most media is 9:16. */ + static final int POST_W = 128, POST_H = 228; + + private Ui() { + } + + static int dp(Context c, float v) { + return Math.round(v * c.getResources().getDisplayMetrics().density); + } + + static TextView text(Context c, String s, int sp) { + TextView t = new TextView(c); + t.setText(s); + t.setTextSize(sp); + t.setTextColor(Color.WHITE); + return t; + } + + static LinearLayout column(Context c) { + LinearLayout box = new LinearLayout(c); + box.setOrientation(LinearLayout.VERTICAL); + box.setGravity(Gravity.CENTER); + int p = dp(c, 32); + box.setPadding(p, p, p, p); + return box; + } + + static LinearLayout row(Context c) { + LinearLayout row = new LinearLayout(c); + row.setGravity(Gravity.CENTER); + return row; + } + + static EditText field(Context c, String hint, int inputType) { + EditText e = new EditText(c); + e.setHint(hint); + e.setInputType(inputType); + e.setTextColor(Color.WHITE); + e.setHintTextColor(Color.GRAY); + e.setMinWidth(dp(c, 400)); + e.setSingleLine(true); + return e; + } + + static int password() { + return InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD; + } + + /** Focus ring and a small zoom: the only focus feedback on a TV. */ + static void focusable(View v) { + v.setFocusable(true); + v.setForeground(v.getContext().getDrawable(R.drawable.focus_ring)); + v.setOnFocusChangeListener((x, has) -> zoom(x, has)); + } + + private static void zoom(View v, boolean in) { + v.animate().scaleX(in ? 1.08f : 1f).scaleY(in ? 1.08f : 1f).setDuration(120).start(); + } + + /** Icon-only button; its label shows under it while it has focus. Child 0 is the ImageView. */ + static LinearLayout iconButton(Context c, int icon, String label, View.OnClickListener click) { + LinearLayout box = new LinearLayout(c); + box.setOrientation(LinearLayout.VERTICAL); + box.setGravity(Gravity.CENTER_HORIZONTAL); + int p = dp(c, 10); + box.setPadding(p, p, p, p / 2); + ImageView image = new ImageView(c); + image.setImageResource(icon); + image.setContentDescription(label); + int s = dp(c, 36); + box.addView(image, new LinearLayout.LayoutParams(s, s)); + TextView t = text(c, label, 12); + t.setVisibility(View.INVISIBLE); + box.addView(t); + box.setFocusable(true); + box.setForeground(c.getDrawable(R.drawable.focus_ring)); + box.setOnFocusChangeListener((v, has) -> { + t.setVisibility(has ? View.VISIBLE : View.INVISIBLE); + zoom(v, has); + }); + box.setOnClickListener(click); + return box; + } + + static void blur(ImageView v, float radius) { + v.setRenderEffect(RenderEffect.createBlurEffect(radius, radius, Shader.TileMode.CLAMP)); + } + + static RecyclerView list(Context c, int orientation) { + RecyclerView rv = new RecyclerView(c); + rv.setLayoutManager(new LinearLayoutManager(c, orientation, false)); + int p = dp(c, 24); + rv.setPadding(p, 0, p, 0); + rv.setClipToPadding(false); + return rv; + } + + static RecyclerView grid(Context c, List<TileAdapter.Tile> tiles, int w, int h, int span) { + RecyclerView rv = new RecyclerView(c); + rv.setLayoutManager(new GridLayoutManager(c, span)); + rv.setAdapter(new TileAdapter(tiles, w, h)); + int p = dp(c, 24); + rv.setPadding(p, p, p, p); + rv.setClipToPadding(false); + return rv; + } + + /** Tiles that open the viewer on posts, starting at the chosen one. */ + static List<TileAdapter.Tile> postTiles(Context c, List<Post> posts) { + List<TileAdapter.Tile> tiles = new ArrayList<>(); + for (int i = 0; i < posts.size(); i++) { + int start = i; + tiles.add(TileAdapter.post(posts.get(i), () -> Playlist.open(c, posts, start))); + } + return tiles; + } +} |
