Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 435a4bb550 | |||
|
6e977bca7e |
8 changed files with 435 additions and 258 deletions
|
|
@ -5,6 +5,7 @@ import android.app.Activity
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.media.projection.MediaProjectionManager
|
import android.media.projection.MediaProjectionManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.MotionEvent
|
||||||
import android.view.ViewTreeObserver
|
import android.view.ViewTreeObserver
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.webkit.JavascriptInterface
|
import android.webkit.JavascriptInterface
|
||||||
|
|
@ -54,6 +55,32 @@ class MainActivity : TauriActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWebViewCreate(webView: WebView) {
|
override fun onWebViewCreate(webView: WebView) {
|
||||||
|
webView.settings.apply {
|
||||||
|
setSupportZoom(false)
|
||||||
|
builtInZoomControls = false
|
||||||
|
displayZoomControls = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockingMultiTouch = false
|
||||||
|
webView.setOnTouchListener { _, event ->
|
||||||
|
val shouldBlock = blockingMultiTouch || event.pointerCount > 1
|
||||||
|
|
||||||
|
when (event.actionMasked) {
|
||||||
|
MotionEvent.ACTION_POINTER_DOWN -> {
|
||||||
|
// Cancel the one-finger gesture before consuming the rest of the pinch.
|
||||||
|
MotionEvent.obtain(event).let { cancelEvent ->
|
||||||
|
cancelEvent.action = MotionEvent.ACTION_CANCEL
|
||||||
|
webView.onTouchEvent(cancelEvent)
|
||||||
|
cancelEvent.recycle()
|
||||||
|
}
|
||||||
|
blockingMultiTouch = true
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> blockingMultiTouch = false
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldBlock
|
||||||
|
}
|
||||||
|
|
||||||
NativeAccessibilityBridge.attach(webView)
|
NativeAccessibilityBridge.attach(webView)
|
||||||
mediaWebView = webView
|
mediaWebView = webView
|
||||||
MobileMediaEvents.attach(webView)
|
MobileMediaEvents.attach(webView)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,10 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" href="./favicon.ico" />
|
<link rel="icon" href="./favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
|
||||||
|
/>
|
||||||
<title>Tensamin</title>
|
<title>Tensamin</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import {
|
||||||
SelectContent,
|
SelectContent,
|
||||||
SelectItem,
|
SelectItem,
|
||||||
} from "@methanium/ui";
|
} from "@methanium/ui";
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
|
||||||
import { useIsMobile } from "@methanium/ui";
|
import { useIsMobile } from "@methanium/ui";
|
||||||
import { MobileNavbar } from "./navbar";
|
import { MobileNavbar } from "./navbar";
|
||||||
|
|
||||||
|
|
@ -194,11 +193,7 @@ export default function Sidebar() {
|
||||||
|
|
||||||
const content = (
|
const content = (
|
||||||
<>
|
<>
|
||||||
<SidebarContent
|
<SidebarContent className="pt-2">
|
||||||
className={
|
|
||||||
isTauri() && isMobile ? "pt-[env(safe-area-inset-top)]" : "pt-2"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div className="h-full w-full flex flex-col gap-3 p-2 pt-0!">
|
<div className="h-full w-full flex flex-col gap-3 p-2 pt-0!">
|
||||||
<div>
|
<div>
|
||||||
<Wrapper
|
<Wrapper
|
||||||
|
|
@ -309,7 +304,7 @@ export default function Sidebar() {
|
||||||
data-sidebar="sidebar"
|
data-sidebar="sidebar"
|
||||||
data-slot="sidebar"
|
data-slot="sidebar"
|
||||||
data-mobile="true"
|
data-mobile="true"
|
||||||
className="fixed inset-y-0 left-0 z-50 w-screen bg-sidebar p-0 text-sidebar-foreground"
|
className="fixed inset-y-0 left-0 z-50 w-screen bg-sidebar pt-[var(--ui-safe-area-top)] pr-[var(--ui-safe-area-right)] pl-[var(--ui-safe-area-left)] text-sidebar-foreground"
|
||||||
style={{
|
style={{
|
||||||
transform: openMobile ? "translateX(0)" : "translateX(-100%)",
|
transform: openMobile ? "translateX(0)" : "translateX(-100%)",
|
||||||
opacity: openMobile ? 1 : 0,
|
opacity: openMobile ? 1 : 0,
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,6 @@ import CacheSync from "@tensamin/cache/sync";
|
||||||
import { useStorage } from "@tensamin/storage/context";
|
import { useStorage } from "@tensamin/storage/context";
|
||||||
import { useLocation, useNavigate } from "@tanstack/react-router";
|
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||||
import { useIsMobile, Toaster, TooltipProvider } from "@methanium/ui";
|
import { useIsMobile, Toaster, TooltipProvider } from "@methanium/ui";
|
||||||
import { isTauri } from "@tauri-apps/api/core";
|
|
||||||
import { HotkeysProvider } from "@tensamin/hotkeys";
|
import { HotkeysProvider } from "@tensamin/hotkeys";
|
||||||
|
|
||||||
const wrapper = document.getElementById("root");
|
const wrapper = document.getElementById("root");
|
||||||
|
|
@ -255,16 +254,7 @@ function RootShell() {
|
||||||
designStorageKey={null}
|
designStorageKey={null}
|
||||||
>
|
>
|
||||||
<div className="w-screen h-dvh overflow-hidden">
|
<div className="w-screen h-dvh overflow-hidden">
|
||||||
<Toaster
|
<Toaster position={isMobile ? "top-center" : "bottom-right"} />
|
||||||
position={isMobile ? "top-center" : "bottom-right"}
|
|
||||||
{...(isTauri() && isMobile
|
|
||||||
? {
|
|
||||||
mobileOffset: {
|
|
||||||
top: "env(safe-area-inset-top)",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
: {})}
|
|
||||||
/>
|
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Storage>
|
<Storage>
|
||||||
<HotkeysProvider>
|
<HotkeysProvider>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,11 @@ import { Input, type InputController } from "@methanium/ui/markdown";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardHeader,
|
CardHeader,
|
||||||
|
Drawer,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerTrigger,
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
|
|
@ -331,42 +336,65 @@ export default function InputComponent({
|
||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<Popover open={gifPopoverOpen} onOpenChange={setGifPopoverOpen}>
|
{isMobile ? (
|
||||||
<PopoverTrigger
|
<Drawer open={gifPopoverOpen} onOpenChange={setGifPopoverOpen}>
|
||||||
render={({ onClick }) => (
|
<DrawerTrigger asChild>
|
||||||
<Button
|
<div>
|
||||||
onClick={onClick}
|
<Button className="w-9 h-9! p-0" variant="ghost">
|
||||||
className="w-9 h-9! p-0"
|
<FileVideo size={20} />
|
||||||
variant="ghost"
|
</Button>
|
||||||
>
|
</div>
|
||||||
<FileVideo size={20} />
|
</DrawerTrigger>
|
||||||
</Button>
|
<DrawerContent className="h-[80dvh]">
|
||||||
)}
|
<div className="min-h-0 flex-1 p-3">
|
||||||
/>
|
<GifPicker
|
||||||
<PopoverContent
|
onSelect={(url) => {
|
||||||
ref={gifPopoverRef}
|
void handleSubmit(url, true);
|
||||||
className="relative h-130 w-120 min-h-80 max-h-180 min-w-80 max-w-180 overflow-hidden"
|
setGifPopoverOpen(false);
|
||||||
onTouchMoveCapture={(event) => event.stopPropagation()}
|
}}
|
||||||
onWheelCapture={(event) => event.stopPropagation()}
|
/>
|
||||||
style={{
|
</div>
|
||||||
...gifPopoverSize,
|
</DrawerContent>
|
||||||
maxHeight: gifPopoverSize?.height,
|
</Drawer>
|
||||||
}}
|
) : (
|
||||||
>
|
<Popover open={gifPopoverOpen} onOpenChange={setGifPopoverOpen}>
|
||||||
<div
|
<PopoverTrigger
|
||||||
className="absolute left-0 top-0 z-10 h-4 w-4 cursor-nwse-resize"
|
render={({ onClick }) => (
|
||||||
onPointerDown={handleGifPopoverResizeStart}
|
<Button
|
||||||
|
onClick={onClick}
|
||||||
|
aria-label="Open GIF picker"
|
||||||
|
className="w-9 h-9! p-0"
|
||||||
|
variant="ghost"
|
||||||
|
>
|
||||||
|
<FileVideo size={20} />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<GifPicker
|
<PopoverContent
|
||||||
resizeHeight={gifPopoverSize?.height}
|
ref={gifPopoverRef}
|
||||||
resizeWidth={gifPopoverSize?.width}
|
className="relative h-130 w-120 min-h-80 max-h-180 min-w-80 max-w-180 overflow-hidden"
|
||||||
onSelect={(url) => {
|
onTouchMoveCapture={(event) => event.stopPropagation()}
|
||||||
void handleSubmit(url, true);
|
onWheelCapture={(event) => event.stopPropagation()}
|
||||||
setGifPopoverOpen(false);
|
style={{
|
||||||
|
...gifPopoverSize,
|
||||||
|
maxHeight: gifPopoverSize?.height,
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
</PopoverContent>
|
<div
|
||||||
</Popover>
|
className="absolute left-0 top-0 z-10 h-4 w-4 cursor-nwse-resize"
|
||||||
|
onPointerDown={handleGifPopoverResizeStart}
|
||||||
|
/>
|
||||||
|
<GifPicker
|
||||||
|
resizeHeight={gifPopoverSize?.height}
|
||||||
|
resizeWidth={gifPopoverSize?.width}
|
||||||
|
onSelect={(url) => {
|
||||||
|
void handleSubmit(url, true);
|
||||||
|
setGifPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
|
||||||
|
|
@ -166,9 +166,9 @@ export default function Media({
|
||||||
<DialogContent
|
<DialogContent
|
||||||
showCloseButton={false}
|
showCloseButton={false}
|
||||||
onWheel={handleWheel}
|
onWheel={handleWheel}
|
||||||
className="bg-black/50 border-0! w-screen! h-screen! max-w-screen! rounded-none! p-0!"
|
className="inset-0! h-dvh! w-screen! max-h-none! max-w-none! translate-none! overflow-hidden! rounded-none! border-0! bg-black/50 p-0!"
|
||||||
>
|
>
|
||||||
<div className="relative z-10 flex w-full h-full justify-between p-7 items-start pointer-events-none">
|
<div className="pointer-events-none relative z-10 flex h-full w-full items-start justify-between pt-[max(1.75rem,var(--ui-safe-area-top))] pr-[max(1.75rem,var(--ui-safe-area-right))] pb-[max(1.75rem,var(--ui-safe-area-bottom))] pl-[max(1.75rem,var(--ui-safe-area-left))]">
|
||||||
<div
|
<div
|
||||||
className={`flex gap-2 items-center transition-opacity duration-200 ${zoomLevel === 0 ? "opacity-100" : "pointer-events-none opacity-0"}`}
|
className={`flex gap-2 items-center transition-opacity duration-200 ${zoomLevel === 0 ? "opacity-100" : "pointer-events-none opacity-0"}`}
|
||||||
inert={zoomLevel !== 0}
|
inert={zoomLevel !== 0}
|
||||||
|
|
|
||||||
538
pnpm-lock.yaml
generated
538
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
|
@ -6,5 +6,5 @@ allowBuilds:
|
||||||
electron-winstaller: true
|
electron-winstaller: true
|
||||||
esbuild: true
|
esbuild: true
|
||||||
overrides:
|
overrides:
|
||||||
"@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.28/methanium-ui.tgz"
|
"@methanium/ui": "https://git.methanium.net/methanium/ui/releases/download/0.0.29/methanium-ui.tgz"
|
||||||
mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-a692bed/mtp-0.2.0.tgz"
|
mtp: "https://git.methanium.net/methanium/mtp/releases/download/0.2.0-dev-a692bed/mtp-0.2.0.tgz"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue