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
1 change: 1 addition & 0 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
chmod +x .github/scripts/generate-version.sh
.github/scripts/generate-version.sh
env:
COMMIT_MESSAGE: ${{ github.event.head_commit.message }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: 'Display version info'
Expand Down
86 changes: 72 additions & 14 deletions src-tauri/android-templates/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,87 @@ package io.tohowabohu.librefit

import android.os.Bundle
import android.view.View
import android.webkit.JavascriptInterface
import android.webkit.WebView
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.webkit.WebViewCompat
import androidx.webkit.WebViewFeature

class MainActivity : TauriActivity() {

private val safeArea = SafeAreaProvider()

override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
}

override fun onResume() {
super.onResume()
// Disable WebView overscroll to prevent bouncing that reveals gaps behind system bars
val webView = findWebView(window.decorView)
webView?.overScrollMode = View.OVER_SCROLL_NEVER
}
override fun onWebViewCreate(webView: WebView) {
super.onWebViewCreate(webView)
webView.overScrollMode = View.OVER_SCROLL_NEVER

// CSS env(safe-area-inset-*) is broken on WebView < 140 and unreliable for
// navigation bars even on newer versions. Instead, expose insets via a
// JavaScript interface and inject them as CSS custom properties.
webView.addJavascriptInterface(safeArea, "__SAFE_AREA__")

// Runs at document start on every page load — reads insets from the bridge
// and sets CSS custom properties before the page renders.
if (WebViewFeature.isFeatureSupported(WebViewFeature.DOCUMENT_START_SCRIPT)) {
WebViewCompat.addDocumentStartJavaScript(webView, APPLY_INSETS_JS, setOf("*"))
}

// Update bridge values whenever system insets change (layout, keyboard, rotation).
ViewCompat.setOnApplyWindowInsetsListener(webView) { _, windowInsets ->
val systemBars = windowInsets.getInsets(
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
)
val ime = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
val keyboardVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
val density = resources.displayMetrics.density

safeArea.update(
top = systemBars.top / density,
bottom = (if (keyboardVisible) ime.bottom else systemBars.bottom) / density,
left = systemBars.left / density,
right = systemBars.right / density
)

private fun findWebView(view: View): View? {
if (view is android.webkit.WebView) return view
if (view is android.view.ViewGroup) {
for (i in 0 until view.childCount) {
val found = findWebView(view.getChildAt(i))
if (found != null) return found
}
// Re-inject for dynamic changes (keyboard, rotation) — the document start
// script only runs on page load, not on inset changes.
webView.evaluateJavascript(APPLY_INSETS_JS, null)
windowInsets
}
return null
}

companion object {
private const val APPLY_INSETS_JS =
"(function(){try{var i=JSON.parse(__SAFE_AREA__.getInsets());" +
"var s=document.documentElement.style;" +
"s.setProperty('--safe-area-top',i.top+'px');" +
"s.setProperty('--safe-area-bottom',i.bottom+'px');" +
"s.setProperty('--safe-area-left',i.left+'px');" +
"s.setProperty('--safe-area-right',i.right+'px');}catch(e){}})()"
}
}

/** Thread-safe bridge providing system bar inset values to JavaScript. */
class SafeAreaProvider {
@Volatile private var top: Float = 0f
@Volatile private var bottom: Float = 0f
@Volatile private var left: Float = 0f
@Volatile private var right: Float = 0f

fun update(top: Float, bottom: Float, left: Float, right: Float) {
this.top = top
this.bottom = bottom
this.left = left
this.right = right
}

@JavascriptInterface
fun getInsets(): String =
"""{"top":$top,"bottom":$bottom,"left":$left,"right":$right}"""
}
12 changes: 0 additions & 12 deletions src-tauri/android-templates/colors.xml

This file was deleted.

11 changes: 1 addition & 10 deletions src-tauri/android-templates/setup-android.sh
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,7 @@ else
echo " ⚠️ themes.xml template not found"
fi

# 7. Apply custom colors.xml (app color definitions)
if [ -f "$SCRIPT_DIR/colors.xml" ]; then
echo "🎨 Applying custom colors.xml..."
cp "$SCRIPT_DIR/colors.xml" "$ANDROID_DIR/app/src/main/res/values/colors.xml"
echo " ✅ colors.xml applied (includes primary color)"
else
echo " ⚠️ colors.xml template not found"
fi

# 8. Force correct NDK path and version in local.properties and build.gradle.kts
# 7. Force correct NDK path and version in local.properties and build.gradle.kts
if [ -n "$NDK_PATH" ]; then
echo "🔧 Setting NDK path in local.properties..."

Expand Down
2 changes: 0 additions & 2 deletions src-tauri/android-templates/themes.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.librefit" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="android:windowBackground">@color/base100</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">false</item>
</style>
</resources>
3 changes: 1 addition & 2 deletions src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"height": 800,
"resizable": true,
"title": "librefit",
"width": 420,
"backgroundColor": [249, 248, 246, 255]
"width": 420
}
]
},
Expand Down
32 changes: 10 additions & 22 deletions src/routes/(app)/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,6 @@
debug(`user profile=${JSON.stringify(userContext.user)}`);

useRefresh(() => invalidate('data:dashboardData'));

const cubicOut = 'cubic-bezier(0.33, 1, 0.68, 1)';

// FAB transitioning effect
const portal = (node: HTMLElement) => {
document.body.appendChild(node);
node.style.opacity = '0';
node.style.transition = `opacity 150ms ${cubicOut}`;
setTimeout(() => (node.style.opacity = '1'), 100);
return {
destroy() {
node.style.transition = `opacity 100ms ${cubicOut}`;
node.style.opacity = '0';
setTimeout(() => node.remove(), 100);
}
};
};
</script>

<div class="flex flex-col overflow-x-hidden">
Expand Down Expand Up @@ -313,11 +296,7 @@
</div>
</div>
</div>
<button
use:portal
class="fixed bottom-20 right-4 z-[39] btn btn-xl btn-circle btn-primary shadow-lg"
onclick={modal.openCreate}
>
<button class="fab-button btn btn-xl btn-circle btn-primary shadow-lg" onclick={modal.openCreate}>
<Plus size="1.5em" />
</button>
<!-- Intake creation modal -->
Expand Down Expand Up @@ -408,6 +387,15 @@
</ModalDialog>

<style>
.fab-button {
position: sticky;
bottom: 1rem;
float: right;
margin-right: 1rem;
margin-top: -4rem;
z-index: 39;
}

.journey-bar-track {
height: 0.25rem;
border-radius: 9999px;
Expand Down
2 changes: 1 addition & 1 deletion src/routes/setup/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
</div>

<!-- Content card with overlap -->
<div class="bg-base-100 rounded-t-3xl -mt-6 relative z-10 p-4 pt-6">
<div class="bg-base-100 rounded-t-3xl -mt-6 relative z-10 p-4 pt-6 safe-bottom">
<Setup bind:currentStep bind:recommendation />
</div>
</div>
Expand Down
36 changes: 24 additions & 12 deletions src/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,34 @@
--noise: 1;
}

/* Safe area insets for devices with notches / rounded corners */
html {
background-color: oklch(
98% 0.001 106.423
); /* --color-base-100, neutral fill behind system bars */
}

/* Safe area insets for edge-to-edge display.
*
* On Android, --safe-area-* CSS custom properties are injected from
* MainActivity.kt (reliable on all WebView versions). env(safe-area-inset-*)
* is the fallback for desktop and iOS where the native injection doesn't run. */
body {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
overscroll-behavior: none; /* prevent WebView overscroll bounce */
padding-left: var(--safe-area-left, env(safe-area-inset-left));
padding-right: var(--safe-area-right, env(safe-area-inset-right));
overscroll-behavior: none;
}

/* Page headers that extend behind the status bar */
.safe-top {
padding-top: calc(env(safe-area-inset-top) + 2rem); /* safe-area + original pt-8 */
padding-top: calc(var(--safe-area-top, env(safe-area-inset-top)) + 2rem);
}

/* Bottom safe area for pages outside AppShell (e.g. setup wizard) */
.safe-bottom {
padding-bottom: calc(var(--safe-area-bottom, env(safe-area-inset-bottom)) + 0.5rem);
}

/* Bottom dock safe area handled by AppShell */
/* Override veilchen AppShell's scoped env() with the reliable CSS var */
.app-shell-bottom-nav {
padding-bottom: var(--safe-area-bottom, env(safe-area-inset-bottom)) !important;
}

/* DaisyUI modal-bottom sits at the viewport edge — add safe area padding.
* .modal-box has padding: 1.5rem by default; we preserve it and add the inset. */
.modal-bottom .modal-box {
padding-bottom: calc(var(--safe-area-bottom, env(safe-area-inset-bottom)) + 1.5rem);
}
Loading