Standalone mobile test (#9513)
1
.github/workflows/main-docker.yml
vendored
@@ -2,6 +2,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "standalone"
|
||||
- "feature/update**"
|
||||
- "feature/server_esm**"
|
||||
paths-ignore:
|
||||
|
||||
57
.github/workflows/mobile.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Mobile
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build_android:
|
||||
name: Build Android APK
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- uses: pnpm/action-setup@v6
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v5
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Update build info
|
||||
run: pnpm run chore:update-build-info
|
||||
|
||||
- name: Build client-standalone (webDir for Capacitor)
|
||||
run: pnpm --filter @triliumnext/mobile build
|
||||
|
||||
- name: Sync Capacitor Android project
|
||||
run: pnpm --filter @triliumnext/mobile exec cap sync android
|
||||
|
||||
- name: Assemble debug APK
|
||||
working-directory: apps/mobile/android
|
||||
run: ./gradlew assembleDebug --no-daemon
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: trilium-mobile-debug-apk
|
||||
path: apps/mobile/android/app/build/outputs/apk/debug/*.apk
|
||||
retention-days: 14
|
||||
@@ -301,9 +301,14 @@ export default class BrowserSqlProvider implements DatabaseProvider {
|
||||
* Must be called after `initWasm()` and before `loadFromSahPool()`.
|
||||
* This is async because it acquires OPFS file handles.
|
||||
*
|
||||
* Unlike the legacy OPFS VFS, SAHPool does **not** require SharedArrayBuffer
|
||||
* or COOP/COEP headers — it only needs OPFS itself (a Worker context with
|
||||
* `navigator.storage.getDirectory`). This makes it usable in Capacitor's
|
||||
* Android WebView, which doesn't support cross-origin isolation.
|
||||
*
|
||||
* @param options.directory - OPFS directory for the pool (default: auto-derived from VFS name)
|
||||
* @param options.initialCapacity - Minimum number of file slots (default: 6)
|
||||
* @throws Error if the environment doesn't support SAHPool (no OPFS, no Worker, no COOP/COEP)
|
||||
* @throws Error if the environment doesn't support OPFS (no Worker, or no OPFS API)
|
||||
*/
|
||||
async installSahPool(options: { directory?: string; initialCapacity?: number } = {}): Promise<void> {
|
||||
this.ensureSqlite3();
|
||||
@@ -508,11 +513,11 @@ export default class BrowserSqlProvider implements DatabaseProvider {
|
||||
|
||||
loadFromFile(_path: string, _isReadOnly: boolean): void {
|
||||
// Browser environment doesn't have direct file system access.
|
||||
// Use OPFS for persistent storage.
|
||||
// Use SAHPool or OPFS for persistent storage.
|
||||
throw new Error(
|
||||
"loadFromFile is not supported in browser environment. " +
|
||||
"Use loadFromMemory() for temporary databases, loadFromBuffer() to load from data, " +
|
||||
"or loadFromOpfs() for persistent storage."
|
||||
"loadFromSahPool() (preferred) or loadFromOpfs() for persistent storage."
|
||||
);
|
||||
}
|
||||
|
||||
@@ -728,7 +733,10 @@ export default class BrowserSqlProvider implements DatabaseProvider {
|
||||
private ensureDb(): void {
|
||||
this.ensureSqlite3();
|
||||
if (!this.db) {
|
||||
throw new Error("Database not opened. Call loadFromMemory(), loadFromBuffer(), or loadFromOpfs() first.");
|
||||
throw new Error(
|
||||
"Database not opened. Call loadFromMemory(), loadFromBuffer(), " +
|
||||
"loadFromSahPool(), or loadFromOpfs() first."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -342,13 +342,16 @@ async function initialize(): Promise<void> {
|
||||
console.log("[Worker] SAHPool available, loading persistent database (WAL mode)...");
|
||||
sqlProvider!.loadFromSahPool(dbName);
|
||||
} else if (sqlProvider!.isOpfsAvailable()) {
|
||||
// Fall back to legacy OPFS VFS (no WAL, slower writes)
|
||||
console.warn("[Worker] Using legacy OPFS VFS (no WAL mode). Consider enabling COOP/COEP headers for SAHPool.");
|
||||
// Fall back to legacy OPFS VFS (no WAL, slower writes).
|
||||
// This only kicks in if SAHPool installation failed for some
|
||||
// reason but SharedArrayBuffer + legacy OPFS are both available.
|
||||
console.warn("[Worker] SAHPool unavailable; using legacy OPFS VFS (no WAL mode).");
|
||||
sqlProvider!.loadFromOpfs(dbName);
|
||||
} else {
|
||||
// Fall back to in-memory database (non-persistent)
|
||||
// Fall back to in-memory database (non-persistent).
|
||||
// SAHPool only needs a Worker + OPFS API, so reaching this
|
||||
// branch means the environment lacks OPFS entirely.
|
||||
console.warn("[Worker] OPFS not available, using in-memory database (data will not persist)");
|
||||
console.warn("[Worker] To enable persistence, ensure COOP/COEP headers are set by the server");
|
||||
sqlProvider!.loadFromMemory();
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import NoteTreeWidget from "../widgets/note_tree.js";
|
||||
import NoteWrapperWidget from "../widgets/note_wrapper.js";
|
||||
import NoteDetail from "../widgets/NoteDetail.jsx";
|
||||
import QuickSearchWidget from "../widgets/quick_search.js";
|
||||
import { isMobileApp } from "../services/utils";
|
||||
import ScrollPadding from "../widgets/scroll_padding";
|
||||
import SearchResult from "../widgets/search_result.jsx";
|
||||
import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx";
|
||||
@@ -65,7 +66,8 @@ export default class MobileLayout {
|
||||
.child(<NoteIconWidget />)
|
||||
.child(<NoteTitleWidget />)
|
||||
.child(<NoteBadges />)
|
||||
.optChild(glob.isStandalone, <StandaloneWarningBar />)
|
||||
.optChild(isMobileApp(), <StandaloneWarningBar variant="mobile" />)
|
||||
.optChild(glob.isStandalone && !isMobileApp(), <StandaloneWarningBar />)
|
||||
.child(<MobileDetailMenu />)
|
||||
)
|
||||
.child(
|
||||
|
||||
@@ -149,6 +149,14 @@ export function isPWA() {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` when running inside the native Capacitor mobile app wrapper.
|
||||
* PWAs and regular browsers return `false`.
|
||||
*/
|
||||
export function isMobileApp() {
|
||||
return !!window.Capacitor?.isNativePlatform?.();
|
||||
}
|
||||
|
||||
export function isMac() {
|
||||
return navigator.platform.indexOf("Mac") > -1;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,10 @@ body.setup {
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
body.desktop & {
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.setup-option-card {
|
||||
padding: 1.5em;
|
||||
cursor: pointer;
|
||||
@@ -78,8 +82,12 @@ body.setup {
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5em;
|
||||
font-size: 1.15em;
|
||||
font-weight: normal;
|
||||
|
||||
body.desktop & {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
p:last-of-type {
|
||||
@@ -94,15 +102,23 @@ body.setup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 2em;
|
||||
padding-top: calc(2em + env(safe-area-inset-top));
|
||||
padding-bottom: calc(2em + env(safe-area-inset-bottom));
|
||||
padding-left: calc(2em + env(safe-area-inset-left));
|
||||
padding-right: calc(2em + env(safe-area-inset-right));
|
||||
overflow: auto;
|
||||
|
||||
>.back-button {
|
||||
position: absolute;
|
||||
top: 2em;
|
||||
inset-inline-start: 2em;
|
||||
top: calc(1em + env(safe-area-inset-top));
|
||||
inset-inline-start: 1em;
|
||||
color: var(--muted-text-color);
|
||||
|
||||
body.desktop & {
|
||||
inset-inline-start: 2em;
|
||||
top: 2em;
|
||||
}
|
||||
|
||||
.tn-icon {
|
||||
margin-inline-end: 0.4em;
|
||||
}
|
||||
@@ -116,8 +132,11 @@ body.setup {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 1em;
|
||||
min-height: 0;
|
||||
padding-top: 2em;
|
||||
|
||||
body.desktop & {
|
||||
padding-top: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
&.contentless {
|
||||
@@ -126,13 +145,20 @@ body.setup {
|
||||
}
|
||||
|
||||
>footer {
|
||||
background: var(--main-background-color);
|
||||
position: sticky;
|
||||
bottom: -2rem;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
border-top: 1px solid var(--main-border-color);
|
||||
padding-top: 1rem;
|
||||
margin-inline: -2em;
|
||||
padding-inline: 2em;
|
||||
padding-bottom: 2rem;
|
||||
margin-inline: -2rem;
|
||||
margin-bottom: -2rem;
|
||||
padding-inline: 2rem;
|
||||
}
|
||||
|
||||
>.page-error {
|
||||
@@ -158,9 +184,13 @@ body.setup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
width: 80%;
|
||||
width: 100%;
|
||||
margin-inline: auto;
|
||||
|
||||
body.desktop & {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -187,6 +217,11 @@ body.setup {
|
||||
justify-content: center;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 1.5rem;
|
||||
padding-block: 2em;
|
||||
|
||||
body.desktop & {
|
||||
padding-block: 1em;
|
||||
}
|
||||
|
||||
.tn-icon {
|
||||
font-size: 3em;
|
||||
@@ -223,13 +258,27 @@ body.setup {
|
||||
text-align: center;
|
||||
color: var(--muted-text-color);
|
||||
opacity: 0.6;
|
||||
margin-block: 1rem;
|
||||
}
|
||||
|
||||
.illustration-logo {
|
||||
width: 96px;
|
||||
height: 96px;
|
||||
--size: 128px;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
margin: auto;
|
||||
|
||||
body.desktop & {
|
||||
--size: 96px;
|
||||
}
|
||||
}
|
||||
|
||||
.illustration-icon,
|
||||
.illustration-logo {
|
||||
margin-top: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
body.desktop & {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
@@ -328,21 +377,30 @@ body[dir=rtl] .slide-in-backward {
|
||||
}
|
||||
|
||||
.page.select-language {
|
||||
.dropdownWrapper {
|
||||
padding-bottom: 2em;
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
main {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.dropdownWrapper,
|
||||
.dropdown,
|
||||
.dropdown-menu {
|
||||
.tn-card {
|
||||
width: 100%;
|
||||
margin: 0 auto 2em;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
|
||||
body.desktop & {
|
||||
width: 80%;
|
||||
}
|
||||
}
|
||||
|
||||
.tn-card-body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
box-sizing: border-box;
|
||||
.tn-card-section {
|
||||
overflow: auto;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import Admonition from "./widgets/react/Admonition";
|
||||
import Button from "./widgets/react/Button";
|
||||
import { Card, CardFrame, CardSection } from "./widgets/react/Card";
|
||||
import FormGroup from "./widgets/react/FormGroup";
|
||||
import FormList, { FormListItem } from "./widgets/react/FormList";
|
||||
import { FormListItem } from "./widgets/react/FormList";
|
||||
import FormTextBox from "./widgets/react/FormTextBox";
|
||||
import Icon from "./widgets/react/Icon";
|
||||
|
||||
@@ -24,7 +24,7 @@ async function main() {
|
||||
|
||||
const bodyWrapper = document.createElement("div");
|
||||
bodyWrapper.classList.add("setup-outer-wrapper");
|
||||
document.body.classList.add("setup");
|
||||
document.body.classList.add("setup", window.glob.device || "desktop");
|
||||
if (isElectron()) {
|
||||
document.body.classList.add("electron", `platform-${window.process.platform}`, "background-effects");
|
||||
}
|
||||
@@ -101,16 +101,24 @@ function SelectLanguage({ setState }: { setState: (state: State) => void }) {
|
||||
illustration={<Icon icon="bx bx-globe" className="illustration-icon" />}
|
||||
footer={<Button text={t("setup.continue")} kind="primary" onClick={() => setState("firstOptions")} />}
|
||||
>
|
||||
<FormList onSelect={async (id) => {
|
||||
await i18n.changeLanguage(id);
|
||||
setCurrentLocale(id);
|
||||
const locale = LOCALES.find(l => l.id === id);
|
||||
document.body.dir = locale?.rtl ? "rtl" : "ltr";
|
||||
}}>
|
||||
{filteredLocales.map(locale => (
|
||||
<FormListItem key={locale.id} value={locale.id} active={locale.id === currentLocale}>{locale.name}</FormListItem>
|
||||
))}
|
||||
</FormList>
|
||||
<Card>
|
||||
<CardSection>
|
||||
{filteredLocales.map(locale => (
|
||||
<FormListItem
|
||||
key={locale.id}
|
||||
value={locale.id}
|
||||
active={locale.id === currentLocale}
|
||||
onClick={async () => {
|
||||
await i18n.changeLanguage(locale.id);
|
||||
setCurrentLocale(locale.id);
|
||||
document.body.dir = locale.rtl ? "rtl" : "ltr";
|
||||
}}
|
||||
>
|
||||
{locale.name}
|
||||
</FormListItem>
|
||||
))}
|
||||
</CardSection>
|
||||
</Card>
|
||||
</SetupPage>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1756,6 +1756,10 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
|
||||
max-height: unset;
|
||||
max-width: unset;
|
||||
|
||||
.modal-header {
|
||||
margin-top: env(safe-area-inset-top);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 0;
|
||||
border: 0;
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
"badge_label": "Standalone",
|
||||
"warning_tooltip": "You are running Trilium in standalone mode. Some features are not available, and you may experience issues or data loss. Use the desktop application or self-hosted server for the best experience."
|
||||
},
|
||||
"mobile": {
|
||||
"badge_label": "Mobile",
|
||||
"warning_tooltip": "You are running Trilium in mobile mode. Some features are not available. Use the desktop application or desktop layout for the best experience."
|
||||
},
|
||||
"about": {
|
||||
"version_label": "Version:",
|
||||
"version": "{{appVersion}} (database: {{dbVersion}}, sync protocol: {{syncVersion}})",
|
||||
|
||||
5
apps/client/src/types.d.ts
vendored
@@ -51,6 +51,11 @@ declare global {
|
||||
_noteReady?: PrintReport;
|
||||
|
||||
EXCALIDRAW_ASSET_PATH?: string;
|
||||
|
||||
Capacitor?: {
|
||||
isNativePlatform?: () => boolean;
|
||||
getPlatform?: () => string;
|
||||
};
|
||||
}
|
||||
|
||||
interface WindowEventMap {
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
import Modal from "../react/Modal.js";
|
||||
import "./about.css";
|
||||
|
||||
import type { AppInfo, Contributor, ContributorList } from "@triliumnext/commons";
|
||||
import clsx from "clsx";
|
||||
import type { ComponentChildren } from "preact";
|
||||
import { memo,useMemo } from "preact/compat";
|
||||
import { useCallback, useRef,useState } from "preact/hooks";
|
||||
import { Fragment } from "preact/jsx-runtime";
|
||||
import type React from "react";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
import contributors from "../../../../../contributors.json";
|
||||
import { t } from "../../services/i18n.js";
|
||||
import { formatDateTime } from "../../utils/formatters.js";
|
||||
import openService from "../../services/open.js";
|
||||
import server from "../../services/server.js";
|
||||
import utils from "../../services/utils.js";
|
||||
import openService from "../../services/open.js";
|
||||
import { useState, useCallback, useRef } from "preact/hooks";
|
||||
import type { AppInfo, Contributor, ContributorList } from "@triliumnext/commons";
|
||||
import { formatDateTime } from "../../utils/formatters.js";
|
||||
import { useTooltip, useTriliumEvent } from "../react/hooks.jsx";
|
||||
import Modal from "../react/Modal.js";
|
||||
import { PropertySheet, PropertySheetItem } from "../react/PropertySheet.js";
|
||||
import "./about.css";
|
||||
import { Trans } from "react-i18next";
|
||||
import type React from "react";
|
||||
import contributors from "../../../../../contributors.json";
|
||||
import { Fragment } from "preact/jsx-runtime";
|
||||
import type { ComponentChildren } from "preact";
|
||||
import { useMemo, memo } from "preact/compat";
|
||||
import clsx from "clsx";
|
||||
|
||||
export default function AboutDialog() {
|
||||
const [appInfo, setAppInfo] = useState<AppInfo | null>(null);
|
||||
@@ -55,17 +57,17 @@ export default function AboutDialog() {
|
||||
setAltIcon(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/* Cache the contributor list to prevent its rerendering.
|
||||
* When the icon changes, it triggers a rerender of the dialog. If this happens while an
|
||||
* element with a tooltip is hovered, its tooltip will break. */
|
||||
const CachedContributors = useMemo(() => memo(function CachedContributors() {
|
||||
return <Contributors
|
||||
const CachedContributors = useMemo(() => memo(() => {
|
||||
return <Contributors
|
||||
data={contributors as ContributorList}
|
||||
onHover={createContributorHoverHandler()}
|
||||
/>
|
||||
/>;
|
||||
}), []);
|
||||
|
||||
return (
|
||||
@@ -76,8 +78,8 @@ export default function AboutDialog() {
|
||||
show={isShown}
|
||||
onHidden={() => setIsShown(false)}
|
||||
>
|
||||
<div className="about-dialog-content">
|
||||
|
||||
<div className="about-dialog-content">
|
||||
|
||||
<div className={"icon"} data-icon={altIcon ?? icon} />
|
||||
<h2>Trilium Notes {isNightly && <span className="channel-name">Nightly</span>}</h2>
|
||||
<a className="tn-link" href="https://triliumnotes.org/" target="_blank" rel="noopener noreferrer">
|
||||
@@ -112,23 +114,25 @@ export default function AboutDialog() {
|
||||
</a>
|
||||
</PropertySheetItem>
|
||||
|
||||
<PropertySheetItem label={t("about.data_directory")}>
|
||||
<div style={{wordBreak: "break-all"}}>
|
||||
{appInfo?.dataDirectory && (<DirectoryLink directory={appInfo.dataDirectory} />)}
|
||||
</div>
|
||||
</PropertySheetItem>
|
||||
{appInfo?.dataDirectory && (
|
||||
<PropertySheetItem label={t("about.data_directory")}>
|
||||
<div style={{wordBreak: "break-all"}}>
|
||||
<DirectoryLink directory={appInfo.dataDirectory} />
|
||||
</div>
|
||||
</PropertySheetItem>
|
||||
)}
|
||||
</PropertySheet>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<FooterLink
|
||||
<footer>
|
||||
<FooterLink
|
||||
text="GitHub"
|
||||
url="https://github.com/TriliumNext/Trilium"
|
||||
tooltip={t("about.github_tooltip")}>
|
||||
|
||||
<i className='bx bxl-github'></i>
|
||||
<i className='bx bxl-github' />
|
||||
</FooterLink>
|
||||
|
||||
|
||||
<FooterLink
|
||||
text="AGPL 3.0"
|
||||
url="https://docs.triliumnotes.org/user-guide/misc/license"
|
||||
@@ -144,9 +148,9 @@ export default function AboutDialog() {
|
||||
tooltip={t("about.donate_tooltip")}
|
||||
className="donate-link">
|
||||
|
||||
<i className='bx bx-heart' ></i>
|
||||
<i className='bx bx-heart' />
|
||||
</FooterLink>
|
||||
</footer>
|
||||
</footer>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -160,19 +164,18 @@ function RevisionLink({appInfo}: {appInfo: AppInfo | null}) {
|
||||
}
|
||||
|
||||
function FooterLink(props: {children: ComponentChildren, text: string, url: string, tooltip: string, className?: string}) {
|
||||
|
||||
const linkRef = useRef<HTMLAnchorElement>(null);
|
||||
|
||||
useTooltip(linkRef, {
|
||||
title: props.tooltip,
|
||||
delay: 250,
|
||||
placement: "bottom"
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
return <a ref={linkRef} href={props.url} className={props.className} target="_blank" rel="noopener noreferrer" draggable={false}>
|
||||
{props.children}
|
||||
{props.text}
|
||||
</a>
|
||||
</a>;
|
||||
}
|
||||
|
||||
type HoverCallback = (contributor: Contributor, isHovering: boolean, part: "name" | "role") => void;
|
||||
@@ -181,10 +184,10 @@ function Contributors({data, onHover}: {data: ContributorList, onHover?: HoverCa
|
||||
return data.contributors.map((c, index, array) => {
|
||||
return <Fragment key={c.name}>
|
||||
<ContributorListItem data={c} onHover={onHover} />
|
||||
|
||||
|
||||
{/* Add a comma between items */}
|
||||
{(index < array.length - 1) ? ", " : ". "}
|
||||
</Fragment>
|
||||
</Fragment>;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -208,7 +211,7 @@ function ContributorListItem({data, onHover}: {data: Contributor, onHover?: Hove
|
||||
rel="noopener noreferrer"
|
||||
onMouseEnter={() => onHover?.(data, true, "name")}
|
||||
onMouseLeave={() => onHover?.(data, false, "name")}>
|
||||
|
||||
|
||||
{data.fullName ?? data.name}
|
||||
</a>
|
||||
|
||||
@@ -216,10 +219,10 @@ function ContributorListItem({data, onHover}: {data: Contributor, onHover?: Hove
|
||||
ref={roleRef}
|
||||
onMouseEnter={() => onHover?.(data, true, "role")}
|
||||
onMouseLeave={() => onHover?.(data, false, "role")}>
|
||||
|
||||
|
||||
(<span className="contributor-role">{roleString}</span>)
|
||||
</span>}
|
||||
</>
|
||||
</span>}
|
||||
</>;
|
||||
}
|
||||
|
||||
function DirectoryLink({ directory }: { directory: string}) {
|
||||
@@ -229,8 +232,7 @@ function DirectoryLink({ directory }: { directory: string}) {
|
||||
openService.openDirectory(directory);
|
||||
};
|
||||
|
||||
return <a className="tn-link selectable-text" href="#" onClick={onClick}>{directory}</a>
|
||||
} else {
|
||||
return <span className="selectable-text">{directory}</span>;
|
||||
return <a className="tn-link selectable-text" href="#" onClick={onClick}>{directory}</a>;
|
||||
}
|
||||
}
|
||||
return <span className="selectable-text">{directory}</span>;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,18 @@ import { t } from "../../services/i18n";
|
||||
import { useNoteContext, useTooltip } from "../react/hooks";
|
||||
import "./StandaloneWarningBar.css";
|
||||
|
||||
export default function StandaloneWarningBar() {
|
||||
type WarningBarVariant = "standalone" | "mobile";
|
||||
|
||||
interface WarningBarProps {
|
||||
variant?: WarningBarVariant;
|
||||
}
|
||||
|
||||
export default function StandaloneWarningBar({ variant = "standalone" }: WarningBarProps) {
|
||||
const { noteContext } = useNoteContext();
|
||||
const badgeRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useTooltip(badgeRef, {
|
||||
title: t("standalone.warning_tooltip"),
|
||||
title: t(`${variant}.warning_tooltip`),
|
||||
placement: "top",
|
||||
delay: 200
|
||||
});
|
||||
@@ -21,7 +27,7 @@ export default function StandaloneWarningBar() {
|
||||
return (
|
||||
<div ref={badgeRef} className="standalone-badge">
|
||||
<span className="bx bx-error-circle" />
|
||||
<span className="standalone-badge-text">{t("standalone.badge_label")}</span>
|
||||
<span className="standalone-badge-text">{t(`${variant}.badge_label`)}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
7
apps/mobile/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
.idea/
|
||||
node_modules/
|
||||
.vscode/
|
||||
*.map
|
||||
.DS_Store
|
||||
.sourcemaps
|
||||
dist/
|
||||
29
apps/mobile/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# @triliumnext/mobile
|
||||
|
||||
Capacitor shell that wraps the [`@triliumnext/client-standalone`](../client-standalone/) PWA build as a native mobile app. This package does not ship its own web assets — `webDir` in [capacitor.config.json](./capacitor.config.json) points directly at `../client-standalone/dist`.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Android SDK + an emulator or attached device (set up `ANDROID_HOME` / `ANDROID_SDK_ROOT`).
|
||||
- JDK 17+.
|
||||
- The monorepo installed: `corepack enable && pnpm install` at the repo root.
|
||||
|
||||
## First-time setup
|
||||
|
||||
```bash
|
||||
# 1. Build the standalone web app into apps/client-standalone/dist
|
||||
pnpm --filter @triliumnext/mobile build
|
||||
|
||||
# 2. Generate the native Android project (one-off — commits as apps/mobile/android/)
|
||||
pnpm --filter @triliumnext/mobile exec cap add android
|
||||
```
|
||||
|
||||
## Everyday loop
|
||||
|
||||
```bash
|
||||
pnpm --filter @triliumnext/mobile build # rebuild standalone dist
|
||||
pnpm --filter @triliumnext/mobile sync # copy dist into android/
|
||||
pnpm --filter @triliumnext/mobile run:android # launch on emulator/device
|
||||
# or
|
||||
pnpm --filter @triliumnext/mobile open:android # open Android Studio
|
||||
```
|
||||
101
apps/mobile/android/.gitignore
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
|
||||
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
# Cordova plugins for Capacitor
|
||||
capacitor-cordova-android-plugins
|
||||
|
||||
# Copied web assets
|
||||
app/src/main/assets/public
|
||||
|
||||
# Generated Config files
|
||||
app/src/main/assets/capacitor.config.json
|
||||
app/src/main/assets/capacitor.plugins.json
|
||||
app/src/main/res/xml/config.xml
|
||||
2
apps/mobile/android/app/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build/*
|
||||
!/build/.npmkeep
|
||||
54
apps/mobile/android/app/build.gradle
Normal file
@@ -0,0 +1,54 @@
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
namespace = "org.triliumnotes.trilium"
|
||||
compileSdk = rootProject.ext.compileSdkVersion
|
||||
defaultConfig {
|
||||
applicationId "org.triliumnotes.trilium"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
aaptOptions {
|
||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
|
||||
ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
flatDir{
|
||||
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
|
||||
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||
implementation project(':capacitor-android')
|
||||
testImplementation "junit:junit:$junitVersion"
|
||||
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
|
||||
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
|
||||
implementation project(':capacitor-cordova-android-plugins')
|
||||
}
|
||||
|
||||
apply from: 'capacitor.build.gradle'
|
||||
|
||||
try {
|
||||
def servicesJSON = file('google-services.json')
|
||||
if (servicesJSON.text) {
|
||||
apply plugin: 'com.google.gms.google-services'
|
||||
}
|
||||
} catch(Exception e) {
|
||||
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
|
||||
}
|
||||
19
apps/mobile/android/app/capacitor.build.gradle
Normal file
@@ -0,0 +1,19 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
|
||||
android {
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_21
|
||||
targetCompatibility JavaVersion.VERSION_21
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
||||
dependencies {
|
||||
implementation project(':capacitor-splash-screen')
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (hasProperty('postBuildExtras')) {
|
||||
postBuildExtras()
|
||||
}
|
||||
21
apps/mobile/android/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
41
apps/mobile/android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation|density"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"></meta-data>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package org.triliumnotes.trilium;
|
||||
|
||||
import com.getcapacitor.BridgeActivity;
|
||||
|
||||
public class MainActivity extends BridgeActivity {}
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
BIN
apps/mobile/android/app/src/main/res/drawable/splash.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
BIN
apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
BIN
apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 3.0 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FAFAFA</color>
|
||||
</resources>
|
||||
7
apps/mobile/android/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">Trilium Notes</string>
|
||||
<string name="title_activity_main">Trilium Notes</string>
|
||||
<string name="package_name">org.triliumnotes.trilium</string>
|
||||
<string name="custom_url_scheme">org.triliumnotes.trilium</string>
|
||||
</resources>
|
||||
22
apps/mobile/android/app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||
<item name="android:background">@drawable/splash</item>
|
||||
</style>
|
||||
</resources>
|
||||
5
apps/mobile/android/app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
29
apps/mobile/android/build.gradle
Normal file
@@ -0,0 +1,29 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.13.0'
|
||||
classpath 'com.google.gms:google-services:4.4.4'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
apply from: "variables.gradle"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
6
apps/mobile/android/capacitor.settings.gradle
Normal file
@@ -0,0 +1,6 @@
|
||||
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
|
||||
include ':capacitor-android'
|
||||
project(':capacitor-android').projectDir = new File('../../../node_modules/@capacitor/android/capacitor')
|
||||
|
||||
include ':capacitor-splash-screen'
|
||||
project(':capacitor-splash-screen').projectDir = new File('../../../node_modules/@capacitor/splash-screen/android')
|
||||
22
apps/mobile/android/gradle.properties
Normal file
@@ -0,0 +1,22 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
BIN
apps/mobile/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
7
apps/mobile/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
251
apps/mobile/android/gradlew
vendored
Executable file
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
apps/mobile/android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
5
apps/mobile/android/settings.gradle
Normal file
@@ -0,0 +1,5 @@
|
||||
include ':app'
|
||||
include ':capacitor-cordova-android-plugins'
|
||||
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
|
||||
|
||||
apply from: 'capacitor.settings.gradle'
|
||||
16
apps/mobile/android/variables.gradle
Normal file
@@ -0,0 +1,16 @@
|
||||
ext {
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 36
|
||||
targetSdkVersion = 36
|
||||
androidxActivityVersion = '1.11.0'
|
||||
androidxAppCompatVersion = '1.7.1'
|
||||
androidxCoordinatorLayoutVersion = '1.3.0'
|
||||
androidxCoreVersion = '1.17.0'
|
||||
androidxFragmentVersion = '1.8.9'
|
||||
coreSplashScreenVersion = '1.2.0'
|
||||
androidxWebkitVersion = '1.14.0'
|
||||
junitVersion = '4.13.2'
|
||||
androidxJunitVersion = '1.3.0'
|
||||
androidxEspressoCoreVersion = '3.7.0'
|
||||
cordovaAndroidVersion = '14.0.1'
|
||||
}
|
||||
16
apps/mobile/capacitor.config.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"appId": "org.triliumnotes.trilium",
|
||||
"appName": "Trilium Notes",
|
||||
"webDir": "../client-standalone/dist",
|
||||
"server": {
|
||||
"androidScheme": "https",
|
||||
"hostname": "localhost"
|
||||
},
|
||||
"plugins": {
|
||||
"SplashScreen": {
|
||||
"launchAutoHide": true,
|
||||
"launchShowDuration": 2000,
|
||||
"launchFadeOutDuration": 300
|
||||
}
|
||||
}
|
||||
}
|
||||
22
apps/mobile/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@triliumnext/mobile",
|
||||
"version": "0.102.2",
|
||||
"description": "Capacitor-based mobile app for TriliumNext, built on the standalone client.",
|
||||
"private": true,
|
||||
"license": "AGPL-3.0-only",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm --filter @triliumnext/client-standalone build",
|
||||
"sync": "pnpm build && cap sync",
|
||||
"open:android": "cap open android",
|
||||
"run:android": "cap run android"
|
||||
},
|
||||
"dependencies": {
|
||||
"@capacitor/android": "8.3.1",
|
||||
"@capacitor/core": "8.3.1",
|
||||
"@capacitor/splash-screen": "8.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@capacitor/cli": "8.3.1"
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,12 @@ import utils, { getResourceDir, isDev } from "./services/utils.js";
|
||||
// Allow serving assets even if the installation path contains a hidden (dot-prefixed) directory.
|
||||
const STATIC_OPTIONS: serveStatic.ServeStaticOptions = { dotfiles: "allow" };
|
||||
|
||||
// Capacitor WebView origins for the mobile app — baked in so the mobile client
|
||||
// can sync against any Trilium server without per-server CORS configuration.
|
||||
const MOBILE_APP_ORIGINS = ["https://localhost", "capacitor://localhost"];
|
||||
const DEFAULT_CORS_METHODS = "GET, POST, PUT, DELETE, PATCH, OPTIONS";
|
||||
const DEFAULT_CORS_HEADERS = "Content-Type, Authorization, trilium-cred, pageCount, pageIndex, requestId";
|
||||
|
||||
export default async function buildApp() {
|
||||
const app = express();
|
||||
|
||||
@@ -38,25 +44,9 @@ export default async function buildApp() {
|
||||
app.engine("ejs", (filePath, options, callback) => ejs.renderFile(filePath, options, callback));
|
||||
app.set("view engine", "ejs");
|
||||
|
||||
app.use((req, res, next) => {
|
||||
// set CORS headers
|
||||
if (config["Network"]["corsAllowOrigin"]) {
|
||||
res.header("Access-Control-Allow-Origin", config["Network"]["corsAllowOrigin"]);
|
||||
res.header("Access-Control-Allow-Credentials", "true");
|
||||
}
|
||||
if (config["Network"]["corsAllowMethods"]) {
|
||||
res.header("Access-Control-Allow-Methods", config["Network"]["corsAllowMethods"]);
|
||||
}
|
||||
if (config["Network"]["corsAllowHeaders"]) {
|
||||
res.header("Access-Control-Allow-Headers", config["Network"]["corsAllowHeaders"]);
|
||||
}
|
||||
|
||||
// Handle preflight OPTIONS requests
|
||||
if (req.method === "OPTIONS" && config["Network"]["corsAllowOrigin"]) {
|
||||
res.sendStatus(204);
|
||||
return;
|
||||
}
|
||||
setupCors(app);
|
||||
|
||||
app.use((_req, res, next) => {
|
||||
res.locals.t = t;
|
||||
return next();
|
||||
});
|
||||
@@ -133,3 +123,28 @@ export default async function buildApp() {
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
function setupCors(app: express.Express) {
|
||||
const configuredOrigins = (config["Network"]["corsAllowOrigin"] || "")
|
||||
.split(",")
|
||||
.map(o => o.trim())
|
||||
.filter(Boolean);
|
||||
const allowedOrigins = new Set([...MOBILE_APP_ORIGINS, ...configuredOrigins]);
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const requestOrigin = req.get("origin");
|
||||
if (requestOrigin && allowedOrigins.has(requestOrigin)) {
|
||||
res.header("Access-Control-Allow-Origin", requestOrigin);
|
||||
res.header("Access-Control-Allow-Credentials", "true");
|
||||
res.header("Vary", "Origin");
|
||||
res.header("Access-Control-Allow-Methods", config["Network"]["corsAllowMethods"] || DEFAULT_CORS_METHODS);
|
||||
res.header("Access-Control-Allow-Headers", config["Network"]["corsAllowHeaders"] || DEFAULT_CORS_HEADERS);
|
||||
|
||||
if (req.method === "OPTIONS") {
|
||||
res.sendStatus(204);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
"desktop:build": "pnpm run --filter desktop build",
|
||||
"desktop:start-prod": "pnpm run --filter desktop start-prod",
|
||||
"standalone:start": "pnpm run --filter client-standalone dev",
|
||||
"standalone:build": "pnpm run --filter client-standalone build",
|
||||
"mobile:sync": "pnpm run --filter mobile sync",
|
||||
"desktop:start-prod-no-dir": "pnpm run --filter desktop start-prod-no-dir",
|
||||
"edit-docs:edit-docs": "pnpm run --filter edit-docs edit-docs",
|
||||
"edit-docs:build": "pnpm run --filter edit-docs build",
|
||||
|
||||
308
pnpm-lock.yaml
generated
@@ -808,6 +808,22 @@ importers:
|
||||
specifier: 2.1.2
|
||||
version: 2.1.2
|
||||
|
||||
apps/mobile:
|
||||
dependencies:
|
||||
'@capacitor/android':
|
||||
specifier: 8.3.1
|
||||
version: 8.3.1(@capacitor/core@8.3.1)
|
||||
'@capacitor/core':
|
||||
specifier: 8.3.1
|
||||
version: 8.3.1
|
||||
'@capacitor/splash-screen':
|
||||
specifier: 8.0.1
|
||||
version: 8.0.1(@capacitor/core@8.3.1)
|
||||
devDependencies:
|
||||
'@capacitor/cli':
|
||||
specifier: 8.3.1
|
||||
version: 8.3.1
|
||||
|
||||
apps/server:
|
||||
dependencies:
|
||||
'@ai-sdk/anthropic':
|
||||
@@ -1970,6 +1986,24 @@ packages:
|
||||
'@cacheable/utils@2.3.3':
|
||||
resolution: {integrity: sha512-JsXDL70gQ+1Vc2W/KUFfkAJzgb4puKwwKehNLuB+HrNKWf91O736kGfxn4KujXCCSuh6mRRL4XEB0PkAFjWS0A==}
|
||||
|
||||
'@capacitor/android@8.3.1':
|
||||
resolution: {integrity: sha512-hjskIG8YcBEh3X4yaTXvE9gcqpdcxunTgFruSKnuPxtMxAUzEK4Oq25x0Z1g3cz+MQPc+lRG09R7Ovc+ydKsNw==}
|
||||
peerDependencies:
|
||||
'@capacitor/core': ^8.3.0
|
||||
|
||||
'@capacitor/cli@8.3.1':
|
||||
resolution: {integrity: sha512-1sPGW4THTDfR6YjXwZ0jM7oAfAtciPOHN00qs/3sNAQx1kKrrEYSfDPwCm1/xlAgi0OeL69SiRfw314Ans+1sw==}
|
||||
engines: {node: '>=22.0.0'}
|
||||
hasBin: true
|
||||
|
||||
'@capacitor/core@8.3.1':
|
||||
resolution: {integrity: sha512-UF8ItlHguU1Z6GXfPTeT2gakf+ctNI8pAS1kwSBQlsJMlfD4OPoto/SmKnOxKCQvnF4WRcdWeg6C0zREUNaAQg==}
|
||||
|
||||
'@capacitor/splash-screen@8.0.1':
|
||||
resolution: {integrity: sha512-c/ew/Z3eA7z8l06WoRAtzVF16VwYYrExmHmfGq1Cg675pVzaC/yuucB8/1xG1vhEfnW4fZ1KhSf/kzR1RiVYgg==}
|
||||
peerDependencies:
|
||||
'@capacitor/core': '>=8.0.0'
|
||||
|
||||
'@catppuccin/codemirror@1.0.3':
|
||||
resolution: {integrity: sha512-P1ZCj4DZVLqr/TNz28m3aaF2Elrikpb8OOnzN0Vyf95Un4pTWTkCSvhbskbnJbnNJ87Rfvt3fXoaUj4o55X30Q==}
|
||||
|
||||
@@ -2615,9 +2649,6 @@ packages:
|
||||
'@emnapi/core@1.9.2':
|
||||
resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==}
|
||||
|
||||
'@emnapi/runtime@1.9.0':
|
||||
resolution: {integrity: sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==}
|
||||
|
||||
'@emnapi/runtime@1.9.1':
|
||||
resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==}
|
||||
|
||||
@@ -3776,6 +3807,38 @@ packages:
|
||||
'@types/node':
|
||||
optional: true
|
||||
|
||||
'@ionic/cli-framework-output@2.2.8':
|
||||
resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-array@2.1.6':
|
||||
resolution: {integrity: sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-fs@3.1.7':
|
||||
resolution: {integrity: sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-object@2.1.6':
|
||||
resolution: {integrity: sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-process@2.1.12':
|
||||
resolution: {integrity: sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-stream@3.1.7':
|
||||
resolution: {integrity: sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-subprocess@3.0.1':
|
||||
resolution: {integrity: sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@ionic/utils-terminal@2.3.5':
|
||||
resolution: {integrity: sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@isaacs/fs-minipass@4.0.1':
|
||||
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -5857,6 +5920,9 @@ packages:
|
||||
'@types/fs-extra@11.0.4':
|
||||
resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==}
|
||||
|
||||
'@types/fs-extra@8.1.5':
|
||||
resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==}
|
||||
|
||||
'@types/fs-extra@9.0.13':
|
||||
resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==}
|
||||
|
||||
@@ -6009,6 +6075,9 @@ packages:
|
||||
'@types/sinonjs__fake-timers@8.1.5':
|
||||
resolution: {integrity: sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==}
|
||||
|
||||
'@types/slice-ansi@4.0.0':
|
||||
resolution: {integrity: sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ==}
|
||||
|
||||
'@types/statuses@2.0.6':
|
||||
resolution: {integrity: sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
|
||||
|
||||
@@ -6941,10 +7010,6 @@ packages:
|
||||
'@wxt-dev/storage@1.2.6':
|
||||
resolution: {integrity: sha512-f6AknnpJvhNHW4s0WqwSGCuZAj0fjP3EVNPBO5kB30pY+3Zt/nqZGqJN6FgBLCSkYjPJ8VL1hNX5LMVmvxQoDw==}
|
||||
|
||||
'@xmldom/xmldom@0.8.12':
|
||||
resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
||||
'@xmldom/xmldom@0.9.9':
|
||||
resolution: {integrity: sha512-qycIHAucxy/LXAYIjmLmtQ8q9GPnMbnjG1KXhWm9o5sCr6pOYDATkMPiTNa6/v8eELyqOQ2FsEqeoFYmgv/gJg==}
|
||||
engines: {node: '>=14.6'}
|
||||
@@ -7369,6 +7434,10 @@ packages:
|
||||
bezier-js@6.1.4:
|
||||
resolution: {integrity: sha512-PA0FW9ZpcHbojUCMu28z9Vg/fNkwTj5YhusSAjHHDfHDGLxJ6YUKrAN2vk1fP2MMOxVw4Oko16FMlRGVBGqLKg==}
|
||||
|
||||
big-integer@1.6.52:
|
||||
resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==}
|
||||
engines: {node: '>=0.6'}
|
||||
|
||||
binary-extensions@2.3.0:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -7417,6 +7486,10 @@ packages:
|
||||
bplist-creator@0.0.8:
|
||||
resolution: {integrity: sha512-Za9JKzD6fjLC16oX2wsXfc+qBEhJBJB1YPInoAQpMLhDuj5aVOv1baGeIQSq1Fr3OCqzvsoQcSBSwGId/Ja2PA==}
|
||||
|
||||
bplist-parser@0.3.2:
|
||||
resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==}
|
||||
engines: {node: '>= 5.10.0'}
|
||||
|
||||
brace-expansion@1.1.13:
|
||||
resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==}
|
||||
|
||||
@@ -7831,6 +7904,10 @@ packages:
|
||||
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
commander@12.1.0:
|
||||
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
@@ -8604,6 +8681,10 @@ packages:
|
||||
engines: {node: '>= 12.20.55'}
|
||||
hasBin: true
|
||||
|
||||
elementtree@0.1.7:
|
||||
resolution: {integrity: sha512-wkgGT6kugeQk/P6VZ/f4T+4HB41BVgNBq5CDIZVbQ02nvTVqAiVTbskxxu3eA/X96lMlfYOwnLQpN2v5E1zDEg==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
|
||||
elkjs@0.9.3:
|
||||
resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==}
|
||||
|
||||
@@ -10446,6 +10527,10 @@ packages:
|
||||
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
kleur@4.1.5:
|
||||
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
knockout@3.5.1:
|
||||
resolution: {integrity: sha512-wRJ9I4az0QcsH7A4v4l0enUpkS++MBx0BnL/68KaLzJg7x1qmbjSlwEoCNol7KTYZ+pmtI7Eh2J0Nu6/2Z5J/Q==}
|
||||
|
||||
@@ -11130,10 +11215,6 @@ packages:
|
||||
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
minipass@7.1.2:
|
||||
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
|
||||
minipass@7.1.3:
|
||||
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -11277,6 +11358,11 @@ packages:
|
||||
napi-build-utils@2.0.0:
|
||||
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
|
||||
|
||||
native-run@2.0.3:
|
||||
resolution: {integrity: sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
hasBin: true
|
||||
|
||||
natural-compare@1.4.0:
|
||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||
|
||||
@@ -11657,6 +11743,9 @@ packages:
|
||||
resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
|
||||
engines: {node: '>= 14'}
|
||||
|
||||
package-json-from-dist@1.0.1:
|
||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||
|
||||
package-json@10.0.1:
|
||||
resolution: {integrity: sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -12521,6 +12610,11 @@ packages:
|
||||
deprecated: Rimraf versions prior to v4 are no longer supported
|
||||
hasBin: true
|
||||
|
||||
rimraf@6.1.3:
|
||||
resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==}
|
||||
engines: {node: 20 || >=22}
|
||||
hasBin: true
|
||||
|
||||
roarr@2.15.4:
|
||||
resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==}
|
||||
engines: {node: '>=8.0'}
|
||||
@@ -12776,6 +12870,9 @@ packages:
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
|
||||
sax@1.1.4:
|
||||
resolution: {integrity: sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg==}
|
||||
|
||||
sax@1.6.0:
|
||||
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
|
||||
engines: {node: '>=11.0.0'}
|
||||
@@ -13472,6 +13569,9 @@ packages:
|
||||
thread-stream@3.1.0:
|
||||
resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==}
|
||||
|
||||
through2@4.0.2:
|
||||
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
|
||||
|
||||
through@2.3.8:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
|
||||
@@ -13563,6 +13663,10 @@ packages:
|
||||
resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
trigram-utils@2.0.1:
|
||||
resolution: {integrity: sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ==}
|
||||
|
||||
@@ -13894,6 +13998,10 @@ packages:
|
||||
resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
|
||||
engines: {node: '>=18.12.0'}
|
||||
|
||||
untildify@4.0.0:
|
||||
resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
unused-filename@4.0.1:
|
||||
resolution: {integrity: sha512-ZX6U1J04K1FoSUeoX1OicAhw4d0aro2qo+L8RhJkiGTNtBNkd/Fi1Wxoc9HzcVu6HfOzm0si/N15JjxFmD1z6A==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
@@ -14911,6 +15019,40 @@ snapshots:
|
||||
hashery: 1.4.0
|
||||
keyv: 5.6.0
|
||||
|
||||
'@capacitor/android@8.3.1(@capacitor/core@8.3.1)':
|
||||
dependencies:
|
||||
'@capacitor/core': 8.3.1
|
||||
|
||||
'@capacitor/cli@8.3.1':
|
||||
dependencies:
|
||||
'@ionic/cli-framework-output': 2.2.8
|
||||
'@ionic/utils-subprocess': 3.0.1
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
commander: 12.1.0
|
||||
debug: 4.4.3
|
||||
env-paths: 2.2.1
|
||||
fs-extra: 11.3.4
|
||||
kleur: 4.1.5
|
||||
native-run: 2.0.3
|
||||
open: 8.4.2
|
||||
plist: 3.1.0
|
||||
prompts: 2.4.2
|
||||
rimraf: 6.1.3
|
||||
semver: 7.7.4
|
||||
tar: 7.5.11
|
||||
tslib: 2.8.1
|
||||
xml2js: 0.6.2
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@capacitor/core@8.3.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@capacitor/splash-screen@8.0.1(@capacitor/core@8.3.1)':
|
||||
dependencies:
|
||||
'@capacitor/core': 8.3.1
|
||||
|
||||
'@catppuccin/codemirror@1.0.3':
|
||||
dependencies:
|
||||
'@catppuccin/palette': 1.7.1
|
||||
@@ -16607,11 +16749,6 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.9.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@emnapi/runtime@1.9.1':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -17391,7 +17528,7 @@ snapshots:
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.9.0
|
||||
'@emnapi/runtime': 1.9.2
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-arm64@0.34.5':
|
||||
@@ -17533,6 +17670,82 @@ snapshots:
|
||||
'@types/node': 24.12.2
|
||||
optional: true
|
||||
|
||||
'@ionic/cli-framework-output@2.2.8':
|
||||
dependencies:
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-array@2.1.6':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-fs@3.1.7':
|
||||
dependencies:
|
||||
'@types/fs-extra': 8.1.5
|
||||
debug: 4.4.3
|
||||
fs-extra: 9.1.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-object@2.1.6':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-process@2.1.12':
|
||||
dependencies:
|
||||
'@ionic/utils-object': 2.1.6
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
debug: 4.4.3
|
||||
signal-exit: 3.0.7
|
||||
tree-kill: 1.2.2
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-stream@3.1.7':
|
||||
dependencies:
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-subprocess@3.0.1':
|
||||
dependencies:
|
||||
'@ionic/utils-array': 2.1.6
|
||||
'@ionic/utils-fs': 3.1.7
|
||||
'@ionic/utils-process': 2.1.12
|
||||
'@ionic/utils-stream': 3.1.7
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
cross-spawn: 7.0.6
|
||||
debug: 4.4.3
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@ionic/utils-terminal@2.3.5':
|
||||
dependencies:
|
||||
'@types/slice-ansi': 4.0.0
|
||||
debug: 4.4.3
|
||||
signal-exit: 3.0.7
|
||||
slice-ansi: 4.0.0
|
||||
string-width: 4.2.3
|
||||
strip-ansi: 6.0.1
|
||||
tslib: 2.8.1
|
||||
untildify: 4.0.0
|
||||
wrap-ansi: 7.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@isaacs/fs-minipass@4.0.1':
|
||||
dependencies:
|
||||
minipass: 7.1.3
|
||||
@@ -19787,6 +20000,10 @@ snapshots:
|
||||
'@types/jsonfile': 6.1.4
|
||||
'@types/node': 22.15.21
|
||||
|
||||
'@types/fs-extra@8.1.5':
|
||||
dependencies:
|
||||
'@types/node': 24.12.2
|
||||
|
||||
'@types/fs-extra@9.0.13':
|
||||
dependencies:
|
||||
'@types/node': 24.12.2
|
||||
@@ -19948,6 +20165,8 @@ snapshots:
|
||||
|
||||
'@types/sinonjs__fake-timers@8.1.5': {}
|
||||
|
||||
'@types/slice-ansi@4.0.0': {}
|
||||
|
||||
'@types/statuses@2.0.6':
|
||||
optional: true
|
||||
|
||||
@@ -21940,8 +22159,6 @@ snapshots:
|
||||
async-mutex: 0.5.0
|
||||
dequal: 2.0.3
|
||||
|
||||
'@xmldom/xmldom@0.8.12': {}
|
||||
|
||||
'@xmldom/xmldom@0.9.9': {}
|
||||
|
||||
'@xtuc/ieee754@1.2.0': {}
|
||||
@@ -22364,6 +22581,8 @@ snapshots:
|
||||
|
||||
bezier-js@6.1.4: {}
|
||||
|
||||
big-integer@1.6.52: {}
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
bindings@1.5.0:
|
||||
@@ -22432,6 +22651,10 @@ snapshots:
|
||||
stream-buffers: 2.2.0
|
||||
optional: true
|
||||
|
||||
bplist-parser@0.3.2:
|
||||
dependencies:
|
||||
big-integer: 1.6.52
|
||||
|
||||
brace-expansion@1.1.13:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
@@ -23024,6 +23247,8 @@ snapshots:
|
||||
|
||||
commander@11.1.0: {}
|
||||
|
||||
commander@12.1.0: {}
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
commander@2.9.0:
|
||||
@@ -23871,6 +24096,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
elementtree@0.1.7:
|
||||
dependencies:
|
||||
sax: 1.1.4
|
||||
|
||||
elkjs@0.9.3: {}
|
||||
|
||||
emitter-listener@1.1.2:
|
||||
@@ -25018,7 +25247,7 @@ snapshots:
|
||||
glob@13.0.0:
|
||||
dependencies:
|
||||
minimatch: 10.2.4
|
||||
minipass: 7.1.2
|
||||
minipass: 7.1.3
|
||||
path-scurry: 2.0.0
|
||||
|
||||
glob@13.0.6:
|
||||
@@ -26127,6 +26356,8 @@ snapshots:
|
||||
|
||||
kleur@3.0.3: {}
|
||||
|
||||
kleur@4.1.5: {}
|
||||
|
||||
knockout@3.5.1: {}
|
||||
|
||||
kolorist@1.8.0: {}
|
||||
@@ -27057,8 +27288,6 @@ snapshots:
|
||||
dependencies:
|
||||
yallist: 4.0.0
|
||||
|
||||
minipass@7.1.2: {}
|
||||
|
||||
minipass@7.1.3: {}
|
||||
|
||||
minizlib@2.1.2:
|
||||
@@ -27199,6 +27428,22 @@ snapshots:
|
||||
|
||||
napi-build-utils@2.0.0: {}
|
||||
|
||||
native-run@2.0.3:
|
||||
dependencies:
|
||||
'@ionic/utils-fs': 3.1.7
|
||||
'@ionic/utils-terminal': 2.3.5
|
||||
bplist-parser: 0.3.2
|
||||
debug: 4.4.3
|
||||
elementtree: 0.1.7
|
||||
ini: 4.1.3
|
||||
plist: 3.1.0
|
||||
split2: 4.2.0
|
||||
through2: 4.0.2
|
||||
tslib: 2.8.1
|
||||
yauzl: 2.10.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
natural-compare@1.4.0: {}
|
||||
|
||||
needle@3.5.0:
|
||||
@@ -27638,6 +27883,8 @@ snapshots:
|
||||
degenerator: 5.0.1
|
||||
netmask: 2.0.2
|
||||
|
||||
package-json-from-dist@1.0.1: {}
|
||||
|
||||
package-json@10.0.1:
|
||||
dependencies:
|
||||
ky: 1.14.2
|
||||
@@ -27867,7 +28114,7 @@ snapshots:
|
||||
|
||||
plist@3.1.0:
|
||||
dependencies:
|
||||
'@xmldom/xmldom': 0.8.12
|
||||
'@xmldom/xmldom': 0.9.9
|
||||
base64-js: 1.5.1
|
||||
xmlbuilder: 15.1.1
|
||||
|
||||
@@ -28589,6 +28836,11 @@ snapshots:
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
|
||||
rimraf@6.1.3:
|
||||
dependencies:
|
||||
glob: 13.0.6
|
||||
package-json-from-dist: 1.0.1
|
||||
|
||||
roarr@2.15.4:
|
||||
dependencies:
|
||||
boolean: 3.2.0
|
||||
@@ -28885,6 +29137,8 @@ snapshots:
|
||||
'@parcel/watcher': 2.5.6
|
||||
optional: true
|
||||
|
||||
sax@1.1.4: {}
|
||||
|
||||
sax@1.6.0: {}
|
||||
|
||||
saxes@6.0.0:
|
||||
@@ -29763,6 +30017,10 @@ snapshots:
|
||||
dependencies:
|
||||
real-require: 0.2.0
|
||||
|
||||
through2@4.0.2:
|
||||
dependencies:
|
||||
readable-stream: 3.6.2
|
||||
|
||||
through@2.3.8: {}
|
||||
|
||||
time2fa@1.4.2: {}
|
||||
@@ -29848,6 +30106,8 @@ snapshots:
|
||||
punycode: 2.3.1
|
||||
optional: true
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
trigram-utils@2.0.1:
|
||||
dependencies:
|
||||
collapse-white-space: 2.1.0
|
||||
@@ -30187,6 +30447,8 @@ snapshots:
|
||||
picomatch: 4.0.4
|
||||
webpack-virtual-modules: 0.6.2
|
||||
|
||||
untildify@4.0.0: {}
|
||||
|
||||
unused-filename@4.0.1:
|
||||
dependencies:
|
||||
escape-string-regexp: 5.0.0
|
||||
|
||||
@@ -76,4 +76,61 @@ magick "./png/256x256-dev.png" -background "#ffffff" -gravity center -extent 640
|
||||
# Copy server assets
|
||||
server_dir="$script_dir/../../apps/server"
|
||||
cp "$desktop_forge_dir/app-icon/icon.ico" "$server_dir/src/assets/icon.ico"
|
||||
cp "$desktop_forge_dir/app-icon/icon-dev.ico" "$server_dir/src/assets/icon-dev.ico"
|
||||
cp "$desktop_forge_dir/app-icon/icon-dev.ico" "$server_dir/src/assets/icon-dev.ico"
|
||||
|
||||
# Build Android mobile icons
|
||||
# Legacy launcher: 48/72/96/144/192 px. Adaptive foreground: 108dp canvas with
|
||||
# ~66% safe zone (Android masks the outer ring), scaled per density.
|
||||
mobile_res_dir="$script_dir/../../apps/mobile/android/app/src/main/res"
|
||||
background_color="#FAFAFA"
|
||||
|
||||
# Circular mask rendered via Inkscape for crisp antialiasing at icon sizes.
|
||||
circle_mask_svg=$(mktemp --suffix=.svg)
|
||||
cat > "$circle_mask_svg" <<'EOF'
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||
<circle cx="50" cy="50" r="50" fill="white"/>
|
||||
</svg>
|
||||
EOF
|
||||
trap 'rm -f "$circle_mask_svg"' EXIT
|
||||
|
||||
declare -A launcher_sizes=( [mdpi]=48 [hdpi]=72 [xhdpi]=96 [xxhdpi]=144 [xxxhdpi]=192 )
|
||||
declare -A foreground_sizes=( [mdpi]=108 [hdpi]=162 [xhdpi]=216 [xxhdpi]=324 [xxxhdpi]=432 )
|
||||
|
||||
for density in mdpi hdpi xhdpi xxhdpi xxxhdpi; do
|
||||
launcher_size=${launcher_sizes[$density]}
|
||||
foreground_size=${foreground_sizes[$density]}
|
||||
mipmap_dir="$mobile_res_dir/mipmap-$density"
|
||||
mkdir -p "$mipmap_dir"
|
||||
|
||||
# Adaptive foreground: logo at 50% of the 108dp canvas. The 72dp (66%) safe
|
||||
# zone is the hard clip boundary — any launcher mask (circle, squircle,
|
||||
# teardrop) can trim up to it, so we leave extra margin inside.
|
||||
fg_logo=$(( foreground_size / 2 ))
|
||||
inkscape -w $fg_logo -h $fg_logo "$source_icon_dir/icon-color.svg" -o "$mipmap_dir/_tmp_fg.png"
|
||||
magick "$mipmap_dir/_tmp_fg.png" -background none -gravity center \
|
||||
-extent ${foreground_size}x${foreground_size} "$mipmap_dir/ic_launcher_foreground.png"
|
||||
rm "$mipmap_dir/_tmp_fg.png"
|
||||
|
||||
# Monochrome layer for Android 13+ themed icons. Android ignores RGB and
|
||||
# uses only the alpha channel, tinting it with the system theme color.
|
||||
inkscape -w $fg_logo -h $fg_logo "$source_icon_dir/icon-black.svg" -o "$mipmap_dir/_tmp_mono.png"
|
||||
magick "$mipmap_dir/_tmp_mono.png" -background none -gravity center \
|
||||
-extent ${foreground_size}x${foreground_size} "$mipmap_dir/ic_launcher_monochrome.png"
|
||||
rm "$mipmap_dir/_tmp_mono.png"
|
||||
|
||||
# Legacy square launcher (logo on solid background)
|
||||
sq_logo=$(( launcher_size * 2 / 3 ))
|
||||
inkscape -w $sq_logo -h $sq_logo "$source_icon_dir/icon-color.svg" -o "$mipmap_dir/_tmp.png"
|
||||
magick "$mipmap_dir/_tmp.png" -background "$background_color" -gravity center \
|
||||
-extent ${launcher_size}x${launcher_size} "$mipmap_dir/ic_launcher.png"
|
||||
|
||||
# Legacy round launcher: Inkscape-rendered circle used as alpha mask.
|
||||
# Extract the mask's alpha channel (circle=opaque, bg=transparent) and copy
|
||||
# it onto the square icon.
|
||||
inkscape -w $launcher_size -h $launcher_size "$circle_mask_svg" \
|
||||
-o "$mipmap_dir/_tmp_mask.png"
|
||||
magick "$mipmap_dir/ic_launcher.png" \
|
||||
\( "$mipmap_dir/_tmp_mask.png" -alpha extract \) \
|
||||
-compose CopyOpacity -composite "$mipmap_dir/ic_launcher_round.png"
|
||||
rm "$mipmap_dir/_tmp.png" "$mipmap_dir/_tmp_mask.png"
|
||||
done
|
||||