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
10 changes: 10 additions & 0 deletions app/Devnull/Data/Commit.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,16 @@ public function withReactionsFrom(self $fresh): self
]);
}

/**
* Whether a body is long enough to commit and short enough for devnull, once trimmed.
*/
public static function fits(string $body): bool
{
$length = mb_strlen(trim($body));

return $length >= self::MinLength && $length <= self::MaxLength;
}

/**
* Whether a list would show only the start of a body, as the web's composer
* works it out: more than PreviewLength characters or PreviewLines lines,
Expand Down
2 changes: 1 addition & 1 deletion app/NativeComponents/Concerns/DraftsIssues.php
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public function openIssueFromSheet(): void
$command = $this->sheetIssue === null ? null : IssueCommand::tryFrom($this->sheetIssue);
$body = trim($this->sheetBody);

if ($command === null || $this->openingIssue || ! $this->canDraftIssue() || mb_strlen($body) < Commit::MinLength || mb_strlen($body) > Commit::MaxLength) {
if ($command === null || $this->openingIssue || ! $this->canDraftIssue() || ! Commit::fits($body)) {
return;
}

Expand Down
5 changes: 3 additions & 2 deletions app/NativeComponents/Concerns/OpensCommitSheet.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ public function commitFromSheet(): void
$stashName = $this->sheetStashName();
$body = trim($this->sheetBody);

if ($stashName === null || $this->sheetIssue !== null || mb_strlen($body) < Commit::MinLength || mb_strlen($body) > Commit::MaxLength) {
if ($stashName === null || $this->sheetIssue !== null || ! Commit::fits($body)) {
return;
}

Expand All @@ -196,7 +196,8 @@ public function commitFromSheet(): void
}

/**
* Done: the sheet goes.
* Done, after a report, and Cancel, in the composer's bar: the sheet goes. A draft in it stays stashed, as when
* it's swiped away, so this mustn't forget it.
*/
public function hideCommitSheet(): void
{
Expand Down
107 changes: 57 additions & 50 deletions resources/views/native/partials/commit-sheet.blade.php
Original file line number Diff line number Diff line change
@@ -1,76 +1,83 @@
{{--
The sheet under a commit (see OpensCommitSheet). Quote opens it as a composer: the field, focused, with the commit
being quoted under it as a static card, then the counter and Commit, which waits for a body long enough to make.
If devnull says no, why shows here and the draft stays. The Log's compose button opens the same composer for a new
commit, without the card. A draft stashed last time comes back in it, with the web's stash@{0} and drop on the left
of Commit's row.
The sheet under a commit (see OpensCommitSheet). Quote opens it as a composer: a bar of Cancel, the title and
Commit, which waits for a body long enough to make, over the field, focused, with the commit being quoted under it
as a static card, then the counter. On iOS Cancel is a Liquid Glass button and Commit a prominent one in your
colour; on Android they're text buttons, as in Material 3's full-screen dialog header. The title is centred.
Cancel closes it as a swipe does, keeping the draft. If devnull says no, why shows here and the draft stays. The
Log's compose button opens the same composer for a new commit, without the card. A draft stashed last time comes
back in it, with the web's stash@{0} and drop on the left of the counter's row.

Report commit, in someone else's published commit's More menu, opens it as the web's Report dialog: the private,
not-hidden line, the commit's body clamped to three lines, the four reasons, optional details, and Send report,
which waits for a reason. Once devnull has the report, the sheet says so, with Done. If devnull says no, why shows
here and the form keeps what you wrote.

A new commit can become a `/feature` or `/bug` issue, as on the web (see DraftsIssues): the command's badge over the
field, its placeholder in it, and Open issue for Commit. Opened, the sheet says so, with View issue and Done.
field, its placeholder in it, and Open issue in Commit's place in the bar. Opened, the sheet says so, with View
issue and Done.

It's full height for the forms, and short for the receipts. It's always on the screen, shown or not, so iOS can
slide it away and say when it's gone.
--}}
<native:bottom-sheet ref="sheet" :visible="$sheetOpen" :detents="in_array($sheetView, ['reported', 'issued'], true) ? '0.3' : 'large'" @dismiss="commitSheetClosed">
@if ($sheetCommit || in_array($sheetView, ['compose', 'issued'], true))
@if (in_array($sheetView, ['quote', 'compose'], true))
@php($sheetLength = mb_strlen(trim($sheetBody)))
@php($quoting = $sheetView === 'quote')
@php($issueCommand = $sheetIssue === null ? null : \App\Devnull\IssueCommand::from($sheetIssue))
<native:scroll-view ref="sheet-composer" class="w-full flex-1">
<native:column class="w-full px-5 pt-6 pb-10 gap-4">
<native:text font="semibold" class="text-lg text-theme-ink">{{ $quoting ? 'Quote '.$sheetCommit->author->handle() : 'New commit' }}</native:text>

<native:column class="w-full flex-1">
<native:row class="w-full px-3 ios:mt-4 min-h-[56] items-center gap-2">
<native:button ref="sheet-cancel" variant="ghost" class="min-w-[96] ios:glass" a11y-hint="Keeps your draft for next time" @press="hideCommitSheet">Cancel</native:button>
<native:text font="semibold" class="flex-1 text-center text-base android:text-lg text-theme-ink" :max-lines="1">{{ $quoting ? 'Quote '.$sheetCommit->author->handle() : 'New commit' }}</native:text>
@if ($issueCommand)
@include('native.partials.issue-badge', ['command' => $issueCommand])
{{-- The label stays put while it spins, so the button keeps its size. --}}
<native:button ref="sheet-open-issue" :variant="\Native\Mobile\Platform::isIos() ? $myColour : 'ghost'" class="min-w-[96] ios:glass:prominent" :loading="$openingIssue" :disabled="$openingIssue || ! \App\Devnull\Data\Commit::fits($sheetBody)" @press="openIssueFromSheet">Open issue</native:button>
@else
<native:button ref="sheet-commit" :variant="\Native\Mobile\Platform::isIos() ? $myColour : 'ghost'" class="min-w-[96] ios:glass:prominent" :disabled="! \App\Devnull\Data\Commit::fits($sheetBody)" @press="commitFromSheet">Commit</native:button>
@endif
</native:row>
<native:column class="w-full h-[1] bg-theme-hairline" />
<native:scroll-view ref="sheet-composer" class="w-full flex-1">
<native:column class="w-full px-5 pt-4 pb-10 gap-4">
@if ($sheetError)
<native:text ref="sheet-error" class="w-full text-sm text-theme-destructive">{{ $sheetError }}</native:text>
@endif

<native:bare-text-input
ref="sheet-body"
class="w-full text-theme-ink"
:placeholder="$issueCommand?->placeholder() ?? ($quoting ? 'Add your take' : 'What did you ship?')"
native:model.debounce.150ms="sheetBody"
autofocus
multiline
:max-lines="8"
:max-length="\App\Devnull\Data\Commit::MaxLength"
size="lg"
a11y-label="Your commit"
/>
@if ($issueCommand)
@include('native.partials.issue-badge', ['command' => $issueCommand])
@endif

@if ($quoting)
@include('native.partials.quoted-commit', ['quoted' => $sheetCommit, 'static' => true])
@elseif (! $issueCommand)
@include('native.partials.issue-suggestions', [
'suggestions' => $sheetTyped ? \App\Devnull\IssueCommand::suggested($sheetBody) : [],
'hint' => $issueHintsDismissed ? null : \App\Devnull\IssueCommand::hinted($sheetBody),
])
@endif
<native:bare-text-input
ref="sheet-body"
class="w-full text-theme-ink"
:placeholder="$issueCommand?->placeholder() ?? ($quoting ? 'Add your take' : 'What did you ship?')"
native:model.debounce.150ms="sheetBody"
autofocus
multiline
:max-lines="8"
:max-length="\App\Devnull\Data\Commit::MaxLength"
size="lg"
a11y-label="Your commit"
/>

@if ($sheetError)
<native:text ref="sheet-error" class="w-full text-sm text-theme-destructive">{{ $sheetError }}</native:text>
@endif

<native:row class="w-full gap-3 items-center justify-end">
@if ($sheetRestored)
@include('native.partials.stash-label', ['ref' => 'sheet-stash', 'drop' => 'dropSheetStash'])
<native:spacer />
@if ($quoting)
@include('native.partials.quoted-commit', ['quoted' => $sheetCommit, 'static' => true])
@elseif (! $issueCommand)
@include('native.partials.issue-suggestions', [
'suggestions' => $sheetTyped ? \App\Devnull\IssueCommand::suggested($sheetBody) : [],
'hint' => $issueHintsDismissed ? null : \App\Devnull\IssueCommand::hinted($sheetBody),
])
@endif
@include('native.partials.body-counter', ['body' => $sheetBody, 'ref' => 'sheet-budget'])
@if ($issueCommand)
{{-- The label stays put while it spins, so the button keeps its size. --}}
<native:button ref="sheet-open-issue" :variant="$myColour" :loading="$openingIssue" :disabled="$openingIssue || $sheetLength < \App\Devnull\Data\Commit::MinLength || $sheetLength > \App\Devnull\Data\Commit::MaxLength" @press="openIssueFromSheet">Open issue</native:button>
@else
<native:button ref="sheet-commit" :variant="$myColour" :disabled="$sheetLength < \App\Devnull\Data\Commit::MinLength || $sheetLength > \App\Devnull\Data\Commit::MaxLength" @press="commitFromSheet">Commit</native:button>
@endif
</native:row>
</native:column>
</native:scroll-view>

<native:row class="w-full gap-3 items-center justify-end">
@if ($sheetRestored)
@include('native.partials.stash-label', ['ref' => 'sheet-stash', 'drop' => 'dropSheetStash'])
<native:spacer />
@endif
@include('native.partials.body-counter', ['body' => $sheetBody, 'ref' => 'sheet-budget'])
</native:row>
</native:column>
</native:scroll-view>
</native:column>
@elseif ($sheetView === 'report')
<native:scroll-view ref="report-form" class="w-full flex-1">
<native:column class="w-full px-5 pt-6 pb-10 gap-5">
Expand Down
24 changes: 24 additions & 0 deletions tests/Feature/Screens/ComposeSheetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,30 @@ function sheetCommitDisabled(TestableComponent $screen): bool
Http::assertNotSent(fn (Request $request): bool => $request->method() === 'POST');
});

it('closes the sheet from Cancel in its bar, keeping the draft as a swipe does', function () {
$screen = composeSheetOnLog()->input('sheet-body', 'Half a thought')->tap('sheet-cancel');

expect(nodeByRef($screen, 'sheet')['props']['visible'])->toBeFalse();

$screen->dismissSheet('sheet')->press('composeInSheet');

expect(nodeByRef($screen, 'sheet-body')['props']['value'])->toBe('Half a thought')
->and(nodeByRef($screen, 'sheet-stash'))->not->toBeEmpty();
});

it('draws the bar on Liquid Glass on iOS, and as a full-screen dialog header on Android', function () {
$commit = nodeByRef(composeSheetOnLog(), 'sheet-commit');

expect($commit['props']['glass'] ?? null)->toBe(3)
->and($commit['props']['label'])->toBe('Commit');

fakeApi(['GET /api/v1/log/following' => [pageJson([])]]);
$android = Native::visit('/log', platform: 'android')->press('composeInSheet');

expect(nodeByRef($android, 'sheet-commit')['props'])->toMatchArray(['label' => 'Commit', 'variant' => 'ghost'])
->and(nodeByRef($android, 'sheet-commit')['props'])->not->toHaveKey('glass');
});

it('only commits from a sheet that has a composer in it', function () {
fakeApi(['GET /api/v1/log/following' => [pageJson([commitJson(['id' => 'c1', 'author' => userJson(['id' => 'u2', 'username' => 'ada'])])])]]);

Expand Down
Loading