Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ as the release notes.

## [Unreleased]

### Added

- The image picker also lists the pictures the 3DS camera saves in `sdmc:/DCIM`, one folder down (`DCIM/<folder>/<image>`), after the pictures in `sdmc:/3ds/indigo/images`. Choosing one attaches it from its full path. The picker's top screen names both folders. This needs Wolfram v0.39.0's `wf_attach_scan_images_tree`, which is not released yet, so this PR is blocked on that release. ([#74](https://github.com/ewanc26/indigo/pull/74))

### Changed

- Signing in with an app password needs only the handle: Indigo finds the account's PDS from it, so the Service field is not needed. An empty Service starts from https://bsky.social. The Service field is still used for the browser (OAuth node) sign-in. This needs Wolfram v0.38.0's `wf_agent_login_discovered`, which is not released yet, so this PR is blocked on that release. ([#72](https://github.com/ewanc26/indigo/pull/72))
Expand Down
2 changes: 1 addition & 1 deletion docs/PARITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ An unknown pairing code (a restarted node forgets its pairings) used to be polle
| Lists and members | read-only | implemented, read-only | host |
| Mute and block, with lists | yes | implemented | host |
| Muted words, hide reposts | yes | implemented: Wolfram's matcher (`wf_muted_words_match`), including mute expiry | host |
| Attach an image when composing | yes | implemented: SELECT or the "Add an image" button in compose opens a picker over `sdmc:/3ds/indigo/images` (JPEG and PNG under 950 KB), then asks for alt text; works on posts, replies and quotes. The type and size filter, the folder scan and the upload are Wolfram's (`wolfram/attach.h`) | host only: the compose state, the picker and the layouts (snapshots `compose-image`, `attach-picker`). The upload and a real post have not been run on an emulator or a 3DS (#19 stays open for that). Whether the picker should also look in `sdmc:/DCIM`, where the camera saves, is undecided and needs a console |
| Attach an image when composing | yes | implemented: SELECT or the "Add an image" button in compose opens a picker over `sdmc:/3ds/indigo/images` and one folder down in the camera's `sdmc:/DCIM` (JPEG and PNG under 950 KB), then asks for alt text; works on posts, replies and quotes. The type and size filter, the folder scans and the upload are Wolfram's (`wolfram/attach.h`); the camera folder's scan is `wf_attach_scan_images_tree` (Wolfram v0.39.0, not released yet) | host only: the compose state, the picker over both folders and the layouts (snapshots `compose-image`, `attach-picker`). The upload and a real post have not been run on an emulator or a 3DS, and the DCIM listing has not been seen on a console (#19 stays open for that) |
| On-card cache of timeline and profile data | not listed | issue: #13 | not started |
| Settings screen | n/a | implemented: theme, text scale, alt text, diagnostics, startup feed; reduce motion and high contrast are stored and reserved | host |
| Video, GIFs and animated media | not planned | partial: a video shows its poster frame, decoded at the draw size (400 px cap), with a line saying it cannot play on the 3DS (#69). GIFs are not drawn. | Wolfram's decoder (`wf_image_decode_rgba`) is for still images, and nothing in the 1.5MB image budget accounts for frames. No decoder evaluation has been done, so playback is a decision and not a hardware limit. |
Expand Down
1 change: 1 addition & 0 deletions src/app/app.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ indigo_app_init(indigo_app *app)
/* The memset above leaves text_scale at 0, which is not a valid scale. */
indigo_settings_defaults(&app->settings);
indigo_copy_utf8(app->images_dir, sizeof app->images_dir, INDIGO_IMAGES_DIR);
indigo_copy_utf8(app->camera_dir, sizeof app->camera_dir, INDIGO_CAMERA_DIR);
}

void
Expand Down
5 changes: 3 additions & 2 deletions src/app/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,10 @@ typedef struct {
indigo_profile profile;
indigo_notifications notifications;
indigo_compose compose;
/* Where the attach picker looks; INDIGO_IMAGES_DIR unless a test points it
* elsewhere. */
/* Where the attach picker looks; INDIGO_IMAGES_DIR and INDIGO_CAMERA_DIR
* unless a test points them elsewhere. */
char images_dir[INDIGO_IMAGE_PATH_MAX];
char camera_dir[INDIGO_IMAGE_PATH_MAX];
indigo_search search;
/* The image the viewer is showing, and the screen it was opened from. */
indigo_image image;
Expand Down
5 changes: 3 additions & 2 deletions src/app/app_screens.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ attach_or_detach(indigo_app *app)
return;
}
indigo_app_push_screen(app);
indigo_menu_build_images(&app->menu, app->images_dir);
indigo_menu_build_images(&app->menu, app->images_dir, app->camera_dir);
app->screen = INDIGO_SCREEN_MENU;
}

Expand Down Expand Up @@ -197,7 +197,8 @@ menu_choose(indigo_app *app, unsigned item)
indigo_app_go_back(app);
/* A path that does not fit is dropped rather than cut short into a
* different file. */
if (snprintf(app->compose.image, sizeof app->compose.image, "%s/%s", app->images_dir,
if (snprintf(app->compose.image, sizeof app->compose.image, "%s/%s",
it->camera ? app->camera_dir : app->images_dir,
it->payload) >= (int) sizeof app->compose.image) {
app->compose.image[0] = '\0';
break;
Expand Down
32 changes: 23 additions & 9 deletions src/app/social.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,26 +247,40 @@ indigo_menu_build(indigo_menu *m, const indigo_post *post, const char *account)
}

void
indigo_menu_build_images(indigo_menu *m, const char *dir)
indigo_menu_build_images(indigo_menu *m, const char *images_dir, const char *camera_dir)
{
char names[INDIGO_MENU_MAX - 1][INDIGO_POST_NAME_MAX];
int too_large = 0;
int n;
int app_n = 0;
int camera_n = 0;

memset(m, 0, sizeof *m);
indigo_copy_utf8(m->title, sizeof m->title, "Images");
m->picking_image = true;
if (dir) {
if (images_dir) {
/* So a first-time user finds the folder to copy pictures into. */
mkdir(dir, 0777);
mkdir(images_dir, 0777);
app_n = wf_attach_scan_images(images_dir, &names[0][0], sizeof names[0],
INDIGO_MENU_MAX - 1, &too_large);
}
n = dir ? wf_attach_scan_images(dir, &names[0][0], sizeof names[0], INDIGO_MENU_MAX - 1,
&too_large)
: 0;
for (int i = 0; i < n; i++) {
/* The camera's folder is read one level down (DCIM/<folder>/<image>). It is
* never created here: the camera owns it. Its rows go after the app's. */
if (camera_dir) {
camera_n = wf_attach_scan_images_tree(camera_dir,
&names[0][0] + (size_t) app_n * sizeof names[0],
sizeof names[0], INDIGO_MENU_MAX - 1 - app_n,
&too_large);
}
for (int i = 0; i < app_n + camera_n; i++) {
unsigned before = m->count;

add_item(m, INDIGO_MENU_PICK_IMAGE, names[i], names[i]);
if (m->count > before) {
m->items[before].camera = i >= app_n;
}
}
add_item(m, INDIGO_MENU_CLOSE, n > 0 ? "Close" : "No images found - close", "");
add_item(m, INDIGO_MENU_CLOSE,
app_n + camera_n > 0 ? "Close" : "No images found - close", "");
}

bool
Expand Down
16 changes: 12 additions & 4 deletions src/app/social.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ typedef enum {
/* Where images to attach are looked for. The person copies .jpg and .png files
* here from a computer. */
#define INDIGO_IMAGES_DIR "sdmc:/3ds/indigo/images"
/* The 3DS camera's folder. It saves photos as DCIM/<folder>/<image>, so the
* picker also lists the images one folder down here. */
#define INDIGO_CAMERA_DIR "sdmc:/DCIM"

/* A post being written. Reply and quote both refer to `target`; a reply also
* needs the thread root so it lands in the right conversation. */
Expand Down Expand Up @@ -202,6 +205,9 @@ typedef struct {
indigo_menu_kind kind;
char label[INDIGO_POST_NAME_MAX * 2];
char payload[INDIGO_FACET_TARGET_MAX];
/* For an image picker row: the payload is under the camera folder rather
* than the images folder. */
bool camera;
} indigo_menu_item;

typedef struct {
Expand All @@ -221,10 +227,12 @@ typedef struct {
/* `post` may be NULL when nothing is selected: the facet targets are then
* left out. `account` is the signed-in handle for the profile entry. */
void indigo_menu_build(indigo_menu *m, const indigo_post *post, const char *account);
/* Fill the menu with the postable images in `dir` (see wolfram/attach.h), one
* INDIGO_MENU_PICK_IMAGE item per file whose payload is the file name, and a
* Close item so an empty folder still has a way out. `title` becomes "Images". */
void indigo_menu_build_images(indigo_menu *m, const char *dir);
/* Fill the menu with the postable images in `images_dir` (see wolfram/attach.h)
* and, one folder down, in `camera_dir`. Each file is an INDIGO_MENU_PICK_IMAGE
* item whose payload is its name under that folder ("folder/name" for the
* camera's subfolders), with `camera` set for the camera rows. A Close item
* follows, so an empty picker still has a way out. `title` becomes "Images". */
void indigo_menu_build_images(indigo_menu *m, const char *images_dir, const char *camera_dir);
/* Move the selection, keeping it inside the `rows` visible rows. */
bool indigo_menu_move(indigo_menu *m, int delta, unsigned rows);
/* The item shown in visible row `row`, or NULL when that row is empty. */
Expand Down
3 changes: 2 additions & 1 deletion src/ui/layout_top.c
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,12 @@ build_top_menu(const indigo_app *app, indigo_canvas *c)
indigo_layout_top_title(c, "Add image", "B Close");
indigo_canvas_text(c, 18, 56, 0.6f, COL_TEXT_SOFT, "Choose a picture from");
indigo_canvas_text(c, 18, 78, 0.6f, COL_TEXT, "%s", INDIGO_IMAGES_DIR);
indigo_canvas_text(c, 18, 100, 0.6f, COL_TEXT, "and %s (camera)", INDIGO_CAMERA_DIR);
snprintf(note, sizeof note,
"Copy .jpg or .png files there from a computer; each must be under %d KB. "
"You can describe the picture for people who cannot see it after choosing it.",
WF_ATTACH_MAX_BYTES / 1000);
indigo_layout_top_paragraph(c, 18, 110, 0.55f, COL_TEXT_DIM, 5, note);
indigo_layout_top_paragraph(c, 18, 124, 0.55f, COL_TEXT_DIM, 4, note);
return;
}
indigo_layout_top_title(c, "Menu", "B Close");
Expand Down
105 changes: 105 additions & 0 deletions tests/tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,110 @@ test_attach_image(void)
remove(dir);
}

/* The picker also lists the camera's DCIM/<folder>/<image> files, after the
* app folder's own, and choosing one attaches its full path. */
static void
test_attach_camera_folder(void)
{
char camera[] = "build-host/attach-camera";
char appdir[] = "build-host/attach-camera-app";
char path[256];
indigo_app app;
indigo_input in = {0};
FILE *f;

mkdir("build-host", 0777);
mkdir(camera, 0777);
mkdir(appdir, 0777);
snprintf(path, sizeof path, "%s/100NIN01", camera);
mkdir(path, 0777);
snprintf(path, sizeof path, "%s/100NIN01/IMG_0002.JPG", camera);
f = fopen(path, "wb");
if (f) {
fputs("jpg", f);
fclose(f);
}
snprintf(path, sizeof path, "%s/100NIN01/notes.txt", camera);
f = fopen(path, "wb");
if (f) {
fputs("x", f);
fclose(f);
}
snprintf(path, sizeof path, "%s/top.png", camera);
f = fopen(path, "wb");
if (f) {
fputs("png", f);
fclose(f);
}

indigo_app_init(&app);
indigo_copy_utf8(app.images_dir, sizeof app.images_dir, appdir);
indigo_copy_utf8(app.camera_dir, sizeof app.camera_dir, camera);
app.screen = INDIGO_SCREEN_COMPOSE;
app.compose.mode = INDIGO_COMPOSE_POST;
indigo_copy_utf8(app.compose.text, sizeof app.compose.text, "a post with a camera picture");

in.refresh = true;
indigo_app_update(&app, &in);
in.refresh = false;
CHECK(app.screen == INDIGO_SCREEN_MENU);
CHECK(app.menu.picking_image);
/* Nothing in the app folder; the camera's picture and top-level picture,
* in name order, then Close. The text file is not an image. */
CHECK(app.menu.count == 3);
CHECK(strcmp(app.menu.items[0].label, "100NIN01/IMG_0002.JPG") == 0);
CHECK(app.menu.items[0].camera);
CHECK(strcmp(app.menu.items[1].label, "top.png") == 0);
CHECK(app.menu.items[1].camera);
CHECK(app.menu.items[2].kind == INDIGO_MENU_CLOSE);

/* Choosing the subfolder picture attaches its full path. */
in.confirm = true;
indigo_app_update(&app, &in);
in.confirm = false;
CHECK(app.screen == INDIGO_SCREEN_COMPOSE);
CHECK(indigo_compose_has_image(&app.compose));
snprintf(path, sizeof path, "%s/100NIN01/IMG_0002.JPG", camera);
CHECK(strcmp(app.compose.image, path) == 0);
CHECK(indigo_app_peek_request(&app) == INDIGO_REQUEST_EDIT_IMAGE_ALT);
indigo_app_take_request(&app, NULL);

/* An app-folder picture is still under the app folder. */
snprintf(path, sizeof path, "%s/own.jpg", appdir);
f = fopen(path, "wb");
if (f) {
fputs("jpg", f);
fclose(f);
}
indigo_compose_clear_image(&app.compose);
app.screen = INDIGO_SCREEN_COMPOSE;
in.refresh = true;
indigo_app_update(&app, &in);
in.refresh = false;
CHECK(app.screen == INDIGO_SCREEN_MENU);
CHECK(app.menu.count == 4);
CHECK(strcmp(app.menu.items[0].label, "own.jpg") == 0);
CHECK(!app.menu.items[0].camera);
in.confirm = true;
indigo_app_update(&app, &in);
in.confirm = false;
snprintf(path, sizeof path, "%s/own.jpg", appdir);
CHECK(strcmp(app.compose.image, path) == 0);

snprintf(path, sizeof path, "%s/own.jpg", appdir);
remove(path);
snprintf(path, sizeof path, "%s/100NIN01/IMG_0002.JPG", camera);
remove(path);
snprintf(path, sizeof path, "%s/100NIN01/notes.txt", camera);
remove(path);
snprintf(path, sizeof path, "%s/100NIN01", camera);
remove(path);
snprintf(path, sizeof path, "%s/top.png", camera);
remove(path);
remove(camera);
remove(appdir);
}

static void
test_compose_gate_text_fits(void)
{
Expand Down Expand Up @@ -5391,6 +5495,7 @@ main(void)
test_new_post_reply_gate();
test_compose_gate_text_fits();
test_attach_image();
test_attach_camera_folder();
test_store_file();
test_compose_flow();
test_notifications();
Expand Down
14 changes: 13 additions & 1 deletion tools/snapshot.c
Original file line number Diff line number Diff line change
Expand Up @@ -790,7 +790,19 @@ fill_social(indigo_app *app, const scenario *s)
fclose(f);
}
}
indigo_menu_build_images(&app->menu, "build-host/snapshots/pictures");
/* And the camera's folder: one picture in a DCIM-style subfolder. */
mkdir("build-host/snapshots/camera", 0777);
mkdir("build-host/snapshots/camera/100NIN01", 0777);
snprintf(path, sizeof path, "build-host/snapshots/camera/100NIN01/IMG_0042.JPG");
{
FILE *f = fopen(path, "wb");
if (f) {
fputs("x", f);
fclose(f);
}
}
indigo_menu_build_images(&app->menu, "build-host/snapshots/pictures",
"build-host/snapshots/camera");
} else {
indigo_menu_build(&app->menu, indigo_timeline_selected(&app->timeline),
app->signin.account);
Expand Down
2 changes: 1 addition & 1 deletion wolfram.ref
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.38.0
v0.39.0
Loading