Skip to content
Draft
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
11 changes: 11 additions & 0 deletions app/Devnull/Display.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ final class Display
*/
public const int ElbowLevel = 22;

/**
* How far down the pane under the image viewer's picture can go, in
* points: its author line. Open, it's as tall as the commit it shows.
*/
public const int ViewerPaneDown = 88;

/**
* The room the picture leaves under it for that pane down, in points, with the pane's inset, so the chevrons stay above it.
*/
public const int ViewerPaneRoom = 104;

public static function relativeTime(?CarbonInterface $date, ?CarbonInterface $now = null): string
{
if ($date === null) {
Expand Down
28 changes: 26 additions & 2 deletions app/NativeComponents/Concerns/ShowsCommits.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,18 @@ trait ShowsCommits
*/
public array $viewerImages = [];

/**
* The commit being viewed, shown in the pane under its images, kept as it was when the viewer opened, as the images are.
*/
public ?Commit $viewerCommit = null;

/**
* Where the pane under the viewer's image rests: `fit`, as tall as the
* commit, as the web docks it, or the height in points of its author line
* once you drag it down. It stays where you left it from one image to the next.
*/
public float|string $viewerPaneDetent = 'fit';

/**
* Which of the viewer's images is showing.
*/
Expand Down Expand Up @@ -286,19 +298,22 @@ public function openLinkPreview(string $id): void
* Opens a commit's image full-screen in the app (see partials/image-viewer),
* from the one the row tapped: the image whole rather than cropped, pinch
* zoom between its whole self and four times, a drag to pan, and the
* chevrons under it to move between the commit's other images. An image the
* chevrons under it to move between the commit's other images. The commit
* itself sits under the image in a pane, as the web docks it. An image the
* screen isn't showing, or one at no web address, opens nothing, as it did
* in the browser.
*/
public function openImage(string $id, int $index = 0): void
{
$images = $this->shownCommit($id)?->images ?? [];
$commit = $this->shownCommit($id);
$images = $commit->images ?? [];
$image = $images[$index] ?? null;

if ($image === null || ! SiteLink::isWebUrl($image->url)) {
return;
}

$this->viewerCommit = $commit;
$this->viewerImages = $images;
$this->viewerIndex = $index;
$this->restViewerImage();
Expand All @@ -325,6 +340,14 @@ public function viewPreviousImage(): void
$this->showInViewedImage($this->viewerIndex - 1);
}

/**
* The pane under the viewer's image come to rest: `fit`, or a height in points.
*/
public function viewerPaneMoved(string $detent): void
{
$this->viewerPaneDetent = $detent === 'fit' ? 'fit' : (float) $detent;
}

/**
* The viewer's close, back or outside tap, however the phone reports it:
* the viewer goes and forgets the image and what its gestures left behind,
Expand All @@ -333,6 +356,7 @@ public function viewPreviousImage(): void
public function closeImageViewer(): void
{
$this->viewerOpen = false;
$this->viewerCommit = null;
$this->viewerImages = [];
$this->viewerIndex = 0;
$this->viewerZoom = null;
Expand Down
32 changes: 31 additions & 1 deletion resources/views/native/partials/image-viewer.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,21 @@
fails, shows whatever its platform's loader shows meanwhile — iOS its spinner, Android nothing — since the image
element tells the app nothing about either, there's nothing of the app's to draw here.

The commit sits under the image in a pane, as the web docks it beside its lightbox: its author, then the whole
commit, the pane as tall as it. A drag takes the pane down to its author line, to see more of the picture, and back
up again, as the web's dock button does; the commit stays pinned to the top, cut off at the bottom. The drag runs on
the phone, and only where it comes to rest comes back (see ShowsCommits::viewerPaneMoved()). The pane is drawn in
the viewer itself, so the picture behind keeps its pinch and pan, and the picture keeps the room the pane takes when
it's down, so the chevrons stay above it.

Always in the tree, shown or not, like the sheet, so the phone can animate it away and say when it's gone.
Included beside the sheet, so every screen that shows commits has it once.
--}}
<native:modal ref="image-viewer" :visible="$viewerOpen" a11y-label="Image viewer" @dismiss="closeImageViewer">
@if ($viewerImages !== [])
@php($image = $viewerImages[$viewerIndex])
<native:column class="w-full h-full bg-black safe-area-bottom">
<native:stack class="w-full h-full bg-black">
<native:column class="w-full h-full safe-area-bottom {{ $viewerCommit ? 'pb-['.\App\Devnull\Display::ViewerPaneRoom.']' : '' }}">
<native:gesture-area ref="viewer-gestures" :pinch="$viewerZoom" pinch-min="1" pinch-max="4" :pan-x="$viewerPanX" :pan-y="$viewerPanY" bounded :content-ratio="$image->naturalRatio()" class="w-full flex-1">
<native:column class="w-full h-full items-center justify-center">
<native:image
Expand Down Expand Up @@ -52,5 +60,27 @@ class="w-full h-full"
</native:row>
@endif
</native:column>

@if ($viewerCommit)
@php($author = $viewerCommit->author)
<native:sheet-pane ref="viewer-pane" :detents="\App\Devnull\Display::ViewerPaneDown.',fit'" :detent="$viewerPaneDetent" @change="viewerPaneMoved" class="w-full h-full" a11y-label="The commit">
<native:column class="w-full px-5 pt-2 pb-5 gap-3">
<native:row class="w-full justify-center">
<native:column :width="36" :height="5" class="rounded-full bg-theme-hairline" />
</native:row>
<native:row ref="viewer-author" class="w-full gap-3 items-center">
@include('native.partials.rail-avatar', ['user' => $author, 'lane' => $viewerCommit->lane(), 'size' => 48])
<native:column class="flex-1 gap-[2]">
<native:text font="semibold" class="text-base text-theme-ink" :max-lines="1">{{ $author->name }}</native:text>
<native:text font="mono" class="text-sm text-theme-ink-muted" :max-lines="1">{{ $author->handle() }} · {{ \App\Devnull\Display::relativeTime($viewerCommit->createdAt) }}</native:text>
</native:column>
</native:row>
<native:column ref="viewer-commit" class="w-full gap-1">
@include('native.partials.commit-body', ['commit' => $viewerCommit, 'focused' => true])
</native:column>
</native:column>
</native:sheet-pane>
@endif
</native:stack>
@endif
</native:modal>
37 changes: 37 additions & 0 deletions tests/Feature/Screens/ImageViewerTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use App\Devnull\Display;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Native\Mobile\Events\Gallery\MediaSelected;
Expand Down Expand Up @@ -306,3 +307,39 @@ function imagedCommitImages(): array
->and(nodeByRef($screen, 'image-viewer')['props']['visible'])->toBeTrue()
->and($screen->instance()->sheetOpen)->toBeTrue();
});

it('shows the commit under the image in a pane as tall as it, as the web docks it', function (string $platform) {
$screen = logWithImages(platform: $platform)->tap('image-i1');
$pane = nodeByRef($screen, 'viewer-pane');

expect($pane['type'])->toBe('sheet_pane')
->and($pane['props'])->toMatchArray([
'detents' => Display::ViewerPaneDown.',fit',
'detent' => 'fit',
])
->and(treeShows($pane, 'Local Dev'))->toBeTrue()
->and(treeShows($pane, '@local_dev'))->toBeTrue()
->and(treeShows($pane, 'Shipped the mobile app.'))->toBeTrue();
})->with(['ios', 'android']);

it('keeps the pane where you drag it, from one image to the next', function (string $moved, float|string $kept) {
$screen = logWithImages(2)->tap('image-i1');

$screen->call('viewerPaneMoved', $moved)->tap('viewed-next');

expect(nodeByRef($screen, 'viewer-pane')['props']['detent'])->toBe($kept);
})->with([
'down to its author' => [(string) Display::ViewerPaneDown, (float) Display::ViewerPaneDown],
'back up to the commit' => ['fit', 'fit'],
]);

it('forgets the commit with the image when the viewer closes', function () {
$screen = logWithImages()->tap('image-i1');

expect($screen->instance()->viewerCommit?->id)->toBe('c1');

$screen->dismissSheet('image-viewer');

expect($screen->instance()->viewerCommit)->toBeNull()
->and(findNode($screen->tree(), fn (array $node): bool => ($node['ref'] ?? null) === 'viewer-pane'))->toBeNull();
});
Loading