From 9ef0b5bb5663934f40ae8be56c606b475ab374a2 Mon Sep 17 00:00:00 2001
From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com>
Date: Thu, 29 Oct 2026 21:39:38 +0530
Subject: [PATCH 1/2] Add floating compose button to Log
---
app/NativeComponents/Log.php | 16 +---------------
resources/views/native/log.blade.php | 11 +++++++++++
tests/Feature/Screens/ComposeSheetTest.php | 20 ++++++++++----------
3 files changed, 22 insertions(+), 25 deletions(-)
diff --git a/app/NativeComponents/Log.php b/app/NativeComponents/Log.php
index 9d03448e..cdb9dc26 100644
--- a/app/NativeComponents/Log.php
+++ b/app/NativeComponents/Log.php
@@ -15,8 +15,6 @@
use App\Devnull\ShownTabs;
use App\Devnull\Stash;
use App\Devnull\Timeline;
-use App\Icons\Android;
-use App\Icons\Ios;
use App\Models\PendingWrite;
use App\NativeComponents\Concerns\DrawsSkeletonFirst;
use App\NativeComponents\Concerns\ExpandsThreads;
@@ -32,8 +30,6 @@
use Illuminate\View\View;
use Native\Mobile\Attributes\Poll;
use Native\Mobile\Edge\Element;
-use Native\Mobile\Edge\Layouts\Builders\NavAction;
-use Native\Mobile\Edge\Layouts\Builders\NavBarOptions;
use Native\Mobile\Edge\NativeComponent;
use Native\Mobile\Events\Async\AsyncTaskFailed;
use Native\Mobile\Events\Async\AsyncTaskFinished;
@@ -45,7 +41,7 @@
* EDGE gives PHP no way to scroll a list, so a new key on the list is how it
* gets back to the top.
*
- * The Log has no composer of its own. The nav bar's compose button opens one
+ * The Log has no composer of its own. The floating compose button opens one
* in a sheet (see OpensCommitSheet), which leaves the log where you were in it,
* and the commit goes on top. A field in the list was redrawn with the whole
* log every time it synced, and fast typing lost and reordered letters.
@@ -324,16 +320,6 @@ public function navTitle(): string
return 'devnull';
}
- public function navigationOptions(): ?NavBarOptions
- {
- return NavBarOptions::make()->action(
- NavAction::make('compose')
- ->icon(ios: Ios::SquareAndPencil, android: Android::Edit)
- ->a11yLabel('New commit')
- ->press('composeInSheet'),
- );
- }
-
/**
* Shows a log, from a link such as the empty Following log's Trending, and
* the pager follows. The log you're on goes back to its top.
diff --git a/resources/views/native/log.blade.php b/resources/views/native/log.blade.php
index 84702933..f7599bd4 100644
--- a/resources/views/native/log.blade.php
+++ b/resources/views/native/log.blade.php
@@ -44,3 +44,14 @@ class="w-full flex-1 bg-theme-surface"
+
+@if ($viewer !== null && ! $sheetOpen)
+
+@endif
diff --git a/tests/Feature/Screens/ComposeSheetTest.php b/tests/Feature/Screens/ComposeSheetTest.php
index d0f319ee..975ebe0d 100644
--- a/tests/Feature/Screens/ComposeSheetTest.php
+++ b/tests/Feature/Screens/ComposeSheetTest.php
@@ -13,7 +13,7 @@
});
/**
- * A Following log with one commit of Ada's, its new commit answered with $answer, and the sheet's composer open from the nav bar.
+ * A Following log with one commit of Ada's, its new commit answered with $answer, and the sheet's composer open from the floating button.
*
* @param array{0: mixed, 1?: int}|null $answer
*/
@@ -24,7 +24,7 @@ function composeSheetOnLog(?array $answer = null, string $platform = 'ios'): Tes
'POST /api/v1/commits' => $answer ?? [['data' => commitJson(['id' => '0199e5c4-2222-7000-8000-00000000cafe', 'body' => 'Shipped from the sheet.'])], 201],
]);
- return Native::visit('/log', platform: $platform)->press('composeInSheet');
+ return Native::visit('/log', platform: $platform)->tap('compose-fab');
}
function sheetCommitDisabled(TestableComponent $screen): bool
@@ -32,13 +32,13 @@ function sheetCommitDisabled(TestableComponent $screen): bool
return ! sheetButtonEnabled($screen, 'sheet-commit');
}
-it('opens a composer in the sheet from the nav bar, over the log where you are in it', function () {
+it('opens a composer in the sheet from the floating button, over the log where you are in it', function () {
fakeApi(['GET /api/v1/log/following' => [pageJson([commitJson(['id' => 'c1'])])]]);
$screen = Native::visit('/log', platform: 'ios');
$list = nodeByRef($screen, 'log')['id'];
- $screen->press('composeInSheet')
+ $screen->tap('compose-fab')
->assertNoNavigation()
->assertSee('New commit')
->assertMissingElement('column', fn (array $node): bool => ($node['ref'] ?? null) === 'quote-card');
@@ -183,7 +183,7 @@ function sheetCommitDisabled(TestableComponent $screen): bool
$screen->tap('sheet-cancel');
expect($screen->instance()->sheetClosing)->toBeFalse();
- $screen->press('composeInSheet')->input('sheet-body', 'Once more')->tap('sheet-commit');
+ $screen->tap('compose-fab')->input('sheet-body', 'Once more')->tap('sheet-commit');
expect(nodeByRef($screen, 'sheet')['props']['visible'])->toBeTrue()
->and(nodeByRef($screen, 'sheet-body')['props']['value'])->toBe('Once more');
@@ -214,7 +214,7 @@ function sheetCommitDisabled(TestableComponent $screen): bool
expect(nodeByRef($screen, 'sheet')['props']['visible'])->toBeFalse()
->and($screen->instance()->sheetBody)->toBe('');
- $screen->press('composeInSheet');
+ $screen->tap('compose-fab');
expect(nodeByRef($screen, 'sheet-body')['props']['value'])->toBe('Half a thought')
->and(nodeByRef($screen, 'sheet-stash'))->not->toBeEmpty()
@@ -228,7 +228,7 @@ function sheetCommitDisabled(TestableComponent $screen): bool
expect(nodeByRef($screen, 'sheet')['props']['visible'])->toBeFalse();
- $screen->dismissSheet('sheet')->press('composeInSheet');
+ $screen->dismissSheet('sheet')->tap('compose-fab');
expect(nodeByRef($screen, 'sheet-body')['props']['value'])->toBe('Half a thought')
->and(nodeByRef($screen, 'sheet-stash'))->not->toBeEmpty();
@@ -236,7 +236,7 @@ function sheetCommitDisabled(TestableComponent $screen): bool
it('draws Cancel and Commit as a cross and a check in circles, 16pt in from the top and sides, on both platforms', function (string $platform) {
fakeApi(['GET /api/v1/log/following' => [pageJson([])]]);
- $screen = Native::visit('/log', platform: $platform)->press('composeInSheet')->input('sheet-body', 'Shipped it');
+ $screen = Native::visit('/log', platform: $platform)->tap('compose-fab')->input('sheet-body', 'Shipped it');
$bar = findNode($screen->tree(), fn (array $node): bool => in_array('sheet-cancel', array_column($node['children'] ?? [], 'ref'), true));
$size = fn (string $ref): array => [sheetButtonCircle($screen, $ref)['layout']['width'], sheetButtonCircle($screen, $ref)['layout']['height']];
@@ -249,13 +249,13 @@ function sheetCommitDisabled(TestableComponent $screen): bool
it('puts the cross on glass on iOS and on a tonal circle on Android', function () {
fakeApi(['GET /api/v1/log/following' => [pageJson([])]]);
- $ios = Native::visit('/log', platform: 'ios')->press('composeInSheet');
+ $ios = Native::visit('/log', platform: 'ios')->tap('compose-fab');
$icon = fn (string $ref): ?string => findNode(sheetButtonCircle($ios, $ref), fn (array $node): bool => $node['type'] === 'icon')['props']['name'] ?? null;
expect([$icon('sheet-cancel'), $icon('sheet-commit')])->toBe(['custom:x', 'custom:check'])
->and(sheetButtonCircle($ios, 'sheet-cancel')['props']['glass'] ?? null)->toBe(5);
- $android = Native::visit('/log', platform: 'android')->press('composeInSheet');
+ $android = Native::visit('/log', platform: 'android')->tap('compose-fab');
expect(strtolower(sheetButtonCircle($android, 'sheet-cancel')['style']['bg_color']))->toBe(strtolower(config('native-ui.theme.light.surface-variant')))
->and(sheetButtonCircle($android, 'sheet-cancel')['props'])->not->toHaveKey('glass');
From d9fc1eff8ed2a3cc320375ba23994a370c8af776 Mon Sep 17 00:00:00 2001
From: Punyapal Shah
Date: Thu, 29 Oct 2026 22:27:32 +0530
Subject: [PATCH 2/2] Lower floating compose button
---
resources/views/native/log.blade.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/views/native/log.blade.php b/resources/views/native/log.blade.php
index f7599bd4..529911a0 100644
--- a/resources/views/native/log.blade.php
+++ b/resources/views/native/log.blade.php
@@ -49,7 +49,7 @@ class="w-full flex-1 bg-theme-surface"