diff --git a/.gitignore b/.gitignore index 03573f6..925b220 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,9 @@ Cargo.lock certbot/ nginx/conf.d/app-ssl.conf .env + +mobile/src-tauri/gen/android/build/ +mobile/src-tauri/gen/android/.gradle/ +mobile/src-tauri/gen/android/app/build/ +mobile/src-tauri/gen/android/buildSrc/build/ +mobile/src-tauri/gen/schemas/ diff --git a/Cargo.toml b/Cargo.toml index e92c4cb..72de0f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["backend"] +members = ["backend", "mobile/src-tauri"] resolver = "2" [profile.release] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 4b7a713..ff910c8 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -24,4 +24,5 @@ thiserror = "2.0" time = "0.3" oauth2 = { version = "5.0.0", features = ["reqwest"] } reqwest = { version = "0.13.1", features = ["json"] } -rand = "0.9.2" \ No newline at end of file +rand = "0.9.2" +uuid = { version = "1", features = ["v4"] } \ No newline at end of file diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 970be80..4a3b03a 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,10 +1,14 @@ use axum::{ routing::{get, post, put, delete}, Router, middleware as axum_middleware, + Extension, }; use sea_orm::{sqlx, Database, DatabaseConnection, DbErr}; use sea_orm_migration::prelude::*; use std::net::SocketAddr; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::time::Instant; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; use tower_sessions::{Expiry, SessionManagerLayer, cookie::SameSite}; @@ -14,6 +18,8 @@ use time::Duration; use tower_http::cors::CorsLayer; use axum::http::{Method, HeaderValue}; +pub type MobileTokenStore = Arc>>; + pub mod models; pub mod services; pub mod migration; @@ -168,9 +174,13 @@ pub async fn create_app(db: DatabaseConnection) -> Result { .layer(auth_layer.clone()) .with_state(db.clone()); + let mobile_token_store: MobileTokenStore = Arc::new(Mutex::new(HashMap::new())); + let oauth_routes = Router::new() .route("/auth/google", get(routes::oauth::google_auth)) .route("/auth/google/callback", get(routes::oauth::google_callback)) + .route("/auth/mobile-callback", get(routes::oauth::mobile_callback)) + .layer(Extension(mobile_token_store)) .layer(auth_layer.clone()) .with_state(db.clone()); diff --git a/backend/src/routes/oauth.rs b/backend/src/routes/oauth.rs index 9bc1031..1ab5905 100644 --- a/backend/src/routes/oauth.rs +++ b/backend/src/routes/oauth.rs @@ -1,17 +1,20 @@ use axum::{ extract::{Query, State}, http::StatusCode, - response::Redirect, + response::{Html, IntoResponse, Redirect, Response}, + Extension, Json, }; use axum_login::AuthSession; -use sea_orm::DatabaseConnection; +use sea_orm::{DatabaseConnection, EntityTrait}; use serde::{Deserialize, Serialize}; use tower_sessions::Session; use utoipa::ToSchema; use crate::auth::AuthBackend; +use crate::models::User; use crate::services::OAuthService; +use crate::MobileTokenStore; const CSRF_TOKEN_KEY: &str = "oauth_csrf_token"; const FRONTEND_URL_KEY: &str = "oauth_frontend_url"; @@ -19,6 +22,7 @@ const FRONTEND_URL_KEY: &str = "oauth_frontend_url"; #[derive(Debug, Deserialize, ToSchema)] pub struct GoogleAuthQuery { pub redirect_url: Option, + pub mobile: Option, } #[derive(Debug, Deserialize)] @@ -62,6 +66,13 @@ pub async fn google_auth( .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; } + if query.mobile.unwrap_or(false) { + session + .insert("oauth_mobile", true) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + } + Ok(Json(OAuthUrlResponse { url: auth_url })) } @@ -78,8 +89,9 @@ pub async fn google_callback( mut auth_session: AuthSession, session: Session, State(db): State, + Extension(token_store): Extension, Query(query): Query, -) -> Result { +) -> Result { let stored_csrf: Option = session .get(CSRF_TOKEN_KEY) .await @@ -90,8 +102,15 @@ pub async fn google_callback( .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + let is_mobile: bool = session + .get("oauth_mobile") + .await + .unwrap_or(None) + .unwrap_or(false); + session.remove::(CSRF_TOKEN_KEY).await.ok(); session.remove::(FRONTEND_URL_KEY).await.ok(); + session.remove::("oauth_mobile").await.ok(); if stored_csrf.as_deref() != Some(&query.state) { return Err(StatusCode::UNAUTHORIZED); @@ -134,7 +153,61 @@ pub async fn google_callback( } } - let redirect_url = frontend_url.unwrap_or_else(|| "http://localhost:3000".to_string()); + if is_mobile { + let token = uuid::Uuid::new_v4().to_string(); + { + let mut store = token_store.lock().unwrap(); + store.insert(token.clone(), (user.id, std::time::Instant::now())); + } + let deep_link = format!("com.arrelin.family-budget-android://auth?token={}", token); + let html = format!( + r#" + "#, + deep_link + ); + return Ok(Html(html).into_response()); + } - Ok(Redirect::temporary(&redirect_url)) + let redirect_url = frontend_url.unwrap_or_else(|| "http://localhost:3000".to_string()); + Ok(Redirect::temporary(&redirect_url).into_response()) +} + +#[derive(Debug, Deserialize)] +pub struct MobileCallbackQuery { + pub token: String, +} + +pub async fn mobile_callback( + mut auth_session: AuthSession, + State(db): State, + Extension(token_store): Extension, + Query(query): Query, +) -> Result, StatusCode> { + let user_id = { + let mut store = token_store.lock().unwrap(); + match store.get(&query.token) { + Some((uid, created_at)) if created_at.elapsed().as_secs() < 300 => { + let uid = *uid; + store.remove(&query.token); + uid + } + _ => { + store.remove(&query.token); + return Err(StatusCode::UNAUTHORIZED); + } + } + }; + + let user = User::find_by_id(user_id) + .one(&db) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + .ok_or(StatusCode::UNAUTHORIZED)?; + + auth_session + .login(&user) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; + + Ok(Json(serde_json::json!({"success": true}))) } \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e7e6a72..e5d2f5b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,6 +9,9 @@ "version": "0.0.0", "dependencies": { "@tailwindcss/postcss": "^4.1.18", + "@tauri-apps/api": "^2.10.1", + "@tauri-apps/plugin-deep-link": "^2.4.7", + "@tauri-apps/plugin-shell": "^2.3.5", "axios": "^1.13.2", "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", @@ -1608,6 +1611,34 @@ "tailwindcss": "4.1.18" } }, + "node_modules/@tauri-apps/api": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.10.1.tgz", + "integrity": "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw==", + "license": "Apache-2.0 OR MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@tauri-apps/plugin-deep-link": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-deep-link/-/plugin-deep-link-2.4.7.tgz", + "integrity": "sha512-K0FQlLM6BoV7Ws2xfkh+Tnwi5VZVdkI4Vw/3AGLSf0Xvu2y86AMBzd9w/SpzKhw9ai2B6ES8di/OoGDCExkOzg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, + "node_modules/@tauri-apps/plugin-shell": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-shell/-/plugin-shell-2.3.5.tgz", + "integrity": "sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.10.1" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index a1cffde..e58e3be 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,6 +11,9 @@ }, "dependencies": { "@tailwindcss/postcss": "^4.1.18", + "@tauri-apps/api": "^2.10.1", + "@tauri-apps/plugin-deep-link": "^2.4.7", + "@tauri-apps/plugin-shell": "^2.3.5", "axios": "^1.13.2", "i18next": "^25.8.0", "i18next-browser-languagedetector": "^8.2.0", diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 74ca0db..0176f4f 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -46,10 +46,16 @@ export const authApi = { me: () => apiClient.get('/me'), - getGoogleAuthUrl: (redirectUrl?: string) => + getGoogleAuthUrl: (redirectUrl?: string, mobile?: boolean) => apiClient.get('/auth/google', { - params: redirectUrl ? { redirect_url: redirectUrl } : undefined, + params: { + ...(redirectUrl ? { redirect_url: redirectUrl } : {}), + ...(mobile ? { mobile: true } : {}), + }, }), + + mobileCallback: (token: string) => + apiClient.get('/auth/mobile-callback', { params: { token } }), }; export const familyApi = { diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 21f3932..800f712 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -1,18 +1,62 @@ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { useTranslation } from 'react-i18next'; import { authApi } from '../api/client'; +import { useStore } from '../store/useStore'; import { Loader2, Wallet } from 'lucide-react'; import { FcGoogle } from 'react-icons/fc'; +const isTauriEnv = () => typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window; + +const DEEP_LINK_SCHEME = 'com.arrelin.family-budget-android://auth'; + export default function Login() { const { t } = useTranslation(); + const { setUser } = useStore(); const [loading, setLoading] = useState(false); const [error, setError] = useState(''); + useEffect(() => { + if (!isTauriEnv()) return; + + let unlisten: (() => void) | null = null; + + const setupDeepLink = async () => { + const { onOpenUrl } = await import('@tauri-apps/plugin-deep-link'); + unlisten = await onOpenUrl(async (urls) => { + const url = Array.isArray(urls) ? urls[0] : urls; + if (!url.startsWith(DEEP_LINK_SCHEME)) return; + + const token = new URL(url).searchParams.get('token'); + if (!token) return; + + try { + setLoading(true); + await authApi.mobileCallback(token); + const me = await authApi.me(); + setUser(me.data); + } catch { + setError(t('login.authError')); + setLoading(false); + } + }); + }; + + setupDeepLink(); + return () => { unlisten?.(); }; + }, []); + const handleGoogleLogin = async () => { try { setLoading(true); setError(''); + + if (isTauriEnv()) { + const { open } = await import('@tauri-apps/plugin-shell'); + const response = await authApi.getGoogleAuthUrl(undefined, true); + await open(response.data.url); + return; + } + const currentUrl = window.location.origin; const response = await authApi.getGoogleAuthUrl(currentUrl); window.location.href = response.data.url; diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index b9d9589..3c13057 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -5,6 +5,7 @@ export default defineConfig({ plugins: [react()], server: { port: 5173, + host: process.env.TAURI_DEV_HOST || 'localhost', proxy: { '/api': { target: 'http://localhost:8080', diff --git a/mobile/.gitignore b/mobile/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/mobile/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/mobile/.vscode/extensions.json b/mobile/.vscode/extensions.json new file mode 100644 index 0000000..24d7cc6 --- /dev/null +++ b/mobile/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] +} diff --git a/mobile/package-lock.json b/mobile/package-lock.json new file mode 100644 index 0000000..69ec08a --- /dev/null +++ b/mobile/package-lock.json @@ -0,0 +1,245 @@ +{ + "name": "family-budget-mobile", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "family-budget-mobile", + "devDependencies": { + "@tauri-apps/cli": "^2" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.10.1.tgz", + "integrity": "sha512-jQNGF/5quwORdZSSLtTluyKQ+o6SMa/AUICfhf4egCGFdMHqWssApVgYSbg+jmrZoc8e1DscNvjTnXtlHLS11g==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.10.1", + "@tauri-apps/cli-darwin-x64": "2.10.1", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.1", + "@tauri-apps/cli-linux-arm64-gnu": "2.10.1", + "@tauri-apps/cli-linux-arm64-musl": "2.10.1", + "@tauri-apps/cli-linux-riscv64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-gnu": "2.10.1", + "@tauri-apps/cli-linux-x64-musl": "2.10.1", + "@tauri-apps/cli-win32-arm64-msvc": "2.10.1", + "@tauri-apps/cli-win32-ia32-msvc": "2.10.1", + "@tauri-apps/cli-win32-x64-msvc": "2.10.1" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.10.1.tgz", + "integrity": "sha512-Z2OjCXiZ+fbYZy7PmP3WRnOpM9+Fy+oonKDEmUE6MwN4IGaYqgceTjwHucc/kEEYZos5GICve35f7ZiizgqEnQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.10.1.tgz", + "integrity": "sha512-V/irQVvjPMGOTQqNj55PnQPVuH4VJP8vZCN7ajnj+ZS8Kom1tEM2hR3qbbIRoS3dBKs5mbG8yg1WC+97dq17Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.10.1.tgz", + "integrity": "sha512-Hyzwsb4VnCWKGfTw+wSt15Z2pLw2f0JdFBfq2vHBOBhvg7oi6uhKiF87hmbXOBXUZaGkyRDkCHsdzJcIfoJC2w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.10.1.tgz", + "integrity": "sha512-OyOYs2t5GkBIvyWjA1+h4CZxTcdz1OZPCWAPz5DYEfB0cnWHERTnQ/SLayQzncrT0kwRoSfSz9KxenkyJoTelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.10.1.tgz", + "integrity": "sha512-MIj78PDDGjkg3NqGptDOGgfXks7SYJwhiMh8SBoZS+vfdz7yP5jN18bNaLnDhsVIPARcAhE1TlsZe/8Yxo2zqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.10.1.tgz", + "integrity": "sha512-X0lvOVUg8PCVaoEtEAnpxmnkwlE1gcMDTqfhbefICKDnOTJ5Est3qL0SrWxizDackIOKBcvtpejrSiVpuJI1kw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.10.1.tgz", + "integrity": "sha512-2/12bEzsJS9fAKybxgicCDFxYD1WEI9kO+tlDwX5znWG2GwMBaiWcmhGlZ8fi+DMe9CXlcVarMTYc0L3REIRxw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.10.1.tgz", + "integrity": "sha512-Y8J0ZzswPz50UcGOFuXGEMrxbjwKSPgXftx5qnkuMs2rmwQB5ssvLb6tn54wDSYxe7S6vlLob9vt0VKuNOaCIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.10.1.tgz", + "integrity": "sha512-iSt5B86jHYAPJa/IlYw++SXtFPGnWtFJriHn7X0NFBVunF6zu9+/zOn8OgqIWSl8RgzhLGXQEEtGBdR4wzpVgg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.10.1.tgz", + "integrity": "sha512-gXyxgEzsFegmnWywYU5pEBURkcFN/Oo45EAwvZrHMh+zUSEAvO5E8TXsgPADYm31d1u7OQU3O3HsYfVBf2moHw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.10.1.tgz", + "integrity": "sha512-6Cn7YpPFwzChy0ERz6djKEmUehWrYlM+xTaNzGPgZocw3BD7OfwfWHKVWxXzdjEW2KfKkHddfdxK1XXTYqBRLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/mobile/package.json b/mobile/package.json new file mode 100644 index 0000000..ac3b471 --- /dev/null +++ b/mobile/package.json @@ -0,0 +1,12 @@ +{ + "name": "family-budget-mobile", + "private": true, + "scripts": { + "tauri": "tauri", + "android:dev": "tauri android dev", + "android:build": "tauri android build" + }, + "devDependencies": { + "@tauri-apps/cli": "^2" + } +} diff --git a/mobile/src-tauri/.gitignore b/mobile/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/mobile/src-tauri/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/mobile/src-tauri/Cargo.toml b/mobile/src-tauri/Cargo.toml new file mode 100644 index 0000000..a1f5117 --- /dev/null +++ b/mobile/src-tauri/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "family-budget-android" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +name = "family_budget_android_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } +tauri-plugin-opener = "2" +tauri-plugin-deep-link = "2" +tauri-plugin-shell = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + diff --git a/mobile/src-tauri/build.rs b/mobile/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/mobile/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/mobile/src-tauri/capabilities/default.json b/mobile/src-tauri/capabilities/default.json new file mode 100644 index 0000000..7075013 --- /dev/null +++ b/mobile/src-tauri/capabilities/default.json @@ -0,0 +1,13 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": ["main"], + "permissions": [ + "core:default", + "opener:default", + "shell:default", + "shell:allow-open", + "deep-link:default" + ] +} diff --git a/mobile/src-tauri/gen/android/.editorconfig b/mobile/src-tauri/gen/android/.editorconfig new file mode 100644 index 0000000..ebe51d3 --- /dev/null +++ b/mobile/src-tauri/gen/android/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/.gitignore b/mobile/src-tauri/gen/android/.gitignore new file mode 100644 index 0000000..b248203 --- /dev/null +++ b/mobile/src-tauri/gen/android/.gitignore @@ -0,0 +1,19 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +build +/captures +.externalNativeBuild +.cxx +local.properties +key.properties + +/.tauri +/tauri.settings.gradle \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/app/.gitignore b/mobile/src-tauri/gen/android/app/.gitignore new file mode 100644 index 0000000..6c4d56b --- /dev/null +++ b/mobile/src-tauri/gen/android/app/.gitignore @@ -0,0 +1,6 @@ +/src/main/**/generated +/src/main/jniLibs/**/*.so +/src/main/assets/tauri.conf.json +/tauri.build.gradle.kts +/proguard-tauri.pro +/tauri.properties \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/app/build.gradle.kts b/mobile/src-tauri/gen/android/app/build.gradle.kts new file mode 100644 index 0000000..dee1042 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/build.gradle.kts @@ -0,0 +1,70 @@ +import java.util.Properties + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("rust") +} + +val tauriProperties = Properties().apply { + val propFile = file("tauri.properties") + if (propFile.exists()) { + propFile.inputStream().use { load(it) } + } +} + +android { + compileSdk = 36 + namespace = "com.arrelin.family_budget_android" + defaultConfig { + manifestPlaceholders["usesCleartextTraffic"] = "false" + applicationId = "com.arrelin.family_budget_android" + minSdk = 24 + targetSdk = 36 + versionCode = tauriProperties.getProperty("tauri.android.versionCode", "1").toInt() + versionName = tauriProperties.getProperty("tauri.android.versionName", "1.0") + } + buildTypes { + getByName("debug") { + manifestPlaceholders["usesCleartextTraffic"] = "true" + isDebuggable = true + isJniDebuggable = true + isMinifyEnabled = false + packaging { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so") + jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so") + jniLibs.keepDebugSymbols.add("*/x86/*.so") + jniLibs.keepDebugSymbols.add("*/x86_64/*.so") + } + } + getByName("release") { + isMinifyEnabled = true + proguardFiles( + *fileTree(".") { include("**/*.pro") } + .plus(getDefaultProguardFile("proguard-android-optimize.txt")) + .toList().toTypedArray() + ) + } + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + } +} + +rust { + rootDirRel = "../../../" +} + +dependencies { + implementation("androidx.webkit:webkit:1.14.0") + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("androidx.activity:activity-ktx:1.10.1") + implementation("com.google.android.material:material:1.12.0") + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.4") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") +} + +apply(from = "tauri.build.gradle.kts") \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/app/proguard-rules.pro b/mobile/src-tauri/gen/android/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/app/src/main/AndroidManifest.xml b/mobile/src-tauri/gen/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..50562b2 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/src-tauri/gen/android/app/src/main/java/com/arrelin/family_budget_android/MainActivity.kt b/mobile/src-tauri/gen/android/app/src/main/java/com/arrelin/family_budget_android/MainActivity.kt new file mode 100644 index 0000000..7dd0482 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/java/com/arrelin/family_budget_android/MainActivity.kt @@ -0,0 +1,11 @@ +package com.arrelin.family_budget_android + +import android.os.Bundle +import androidx.activity.enableEdgeToEdge + +class MainActivity : TauriActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() + super.onCreate(savedInstanceState) + } +} diff --git a/mobile/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/mobile/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml b/mobile/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml b/mobile/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..4fc2444 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..28f1aa1 Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..85d0c88 Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..28f1aa1 Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..73e48db Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..13dd214 Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..73e48db Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..1d98044 Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a888b33 Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..1d98044 Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..0818324 Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a2a838e Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..0818324 Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..b18bceb Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3f8a57f Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b18bceb Binary files /dev/null and b/mobile/src-tauri/gen/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/mobile/src-tauri/gen/android/app/src/main/res/values-night/themes.xml b/mobile/src-tauri/gen/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..c2f3c02 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/mobile/src-tauri/gen/android/app/src/main/res/values/colors.xml b/mobile/src-tauri/gen/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/app/src/main/res/values/strings.xml b/mobile/src-tauri/gen/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..50ab6bf --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + family-budget-android + family-budget-android + \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/app/src/main/res/values/themes.xml b/mobile/src-tauri/gen/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..c2f3c02 --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/res/values/themes.xml @@ -0,0 +1,6 @@ + + + + diff --git a/mobile/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml b/mobile/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..782d63b --- /dev/null +++ b/mobile/src-tauri/gen/android/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/mobile/src-tauri/gen/android/build.gradle.kts b/mobile/src-tauri/gen/android/build.gradle.kts new file mode 100644 index 0000000..607240b --- /dev/null +++ b/mobile/src-tauri/gen/android/build.gradle.kts @@ -0,0 +1,22 @@ +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.11.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25") + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +tasks.register("clean").configure { + delete("build") +} + diff --git a/mobile/src-tauri/gen/android/buildSrc/build.gradle.kts b/mobile/src-tauri/gen/android/buildSrc/build.gradle.kts new file mode 100644 index 0000000..5c55bba --- /dev/null +++ b/mobile/src-tauri/gen/android/buildSrc/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + `kotlin-dsl` +} + +gradlePlugin { + plugins { + create("pluginsForCoolKids") { + id = "rust" + implementationClass = "RustPlugin" + } + } +} + +repositories { + google() + mavenCentral() +} + +dependencies { + compileOnly(gradleApi()) + implementation("com.android.tools.build:gradle:8.11.0") +} + diff --git a/mobile/src-tauri/gen/android/buildSrc/src/main/kotlin/BuildTask.kt b/mobile/src-tauri/gen/android/buildSrc/src/main/kotlin/BuildTask.kt new file mode 100644 index 0000000..a3de125 --- /dev/null +++ b/mobile/src-tauri/gen/android/buildSrc/src/main/kotlin/BuildTask.kt @@ -0,0 +1,68 @@ +import java.io.File +import org.apache.tools.ant.taskdefs.condition.Os +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.logging.LogLevel +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction + +open class BuildTask : DefaultTask() { + @Input + var rootDirRel: String? = null + @Input + var target: String? = null + @Input + var release: Boolean? = null + + @TaskAction + fun assemble() { + val executable = """npm"""; + try { + runTauriCli(executable) + } catch (e: Exception) { + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + // Try different Windows-specific extensions + val fallbacks = listOf( + "$executable.exe", + "$executable.cmd", + "$executable.bat", + ) + + var lastException: Exception = e + for (fallback in fallbacks) { + try { + runTauriCli(fallback) + return + } catch (fallbackException: Exception) { + lastException = fallbackException + } + } + throw lastException + } else { + throw e; + } + } + } + + fun runTauriCli(executable: String) { + val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null") + val target = target ?: throw GradleException("target cannot be null") + val release = release ?: throw GradleException("release cannot be null") + val args = listOf("run", "--", "tauri", "android", "android-studio-script"); + + project.exec { + workingDir(File(project.projectDir, rootDirRel)) + executable(executable) + args(args) + if (project.logger.isEnabled(LogLevel.DEBUG)) { + args("-vv") + } else if (project.logger.isEnabled(LogLevel.INFO)) { + args("-v") + } + if (release) { + args("--release") + } + args(listOf("--target", target)) + }.assertNormalExitValue() + } +} \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/buildSrc/src/main/kotlin/RustPlugin.kt b/mobile/src-tauri/gen/android/buildSrc/src/main/kotlin/RustPlugin.kt new file mode 100644 index 0000000..4aa7fca --- /dev/null +++ b/mobile/src-tauri/gen/android/buildSrc/src/main/kotlin/RustPlugin.kt @@ -0,0 +1,85 @@ +import com.android.build.api.dsl.ApplicationExtension +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.get + +const val TASK_GROUP = "rust" + +open class Config { + lateinit var rootDirRel: String +} + +open class RustPlugin : Plugin { + private lateinit var config: Config + + override fun apply(project: Project) = with(project) { + config = extensions.create("rust", Config::class.java) + + val defaultAbiList = listOf("arm64-v8a", "armeabi-v7a", "x86", "x86_64"); + val abiList = (findProperty("abiList") as? String)?.split(',') ?: defaultAbiList + + val defaultArchList = listOf("arm64", "arm", "x86", "x86_64"); + val archList = (findProperty("archList") as? String)?.split(',') ?: defaultArchList + + val targetsList = (findProperty("targetList") as? String)?.split(',') ?: listOf("aarch64", "armv7", "i686", "x86_64") + + extensions.configure { + @Suppress("UnstableApiUsage") + flavorDimensions.add("abi") + productFlavors { + create("universal") { + dimension = "abi" + ndk { + abiFilters += abiList + } + } + defaultArchList.forEachIndexed { index, arch -> + create(arch) { + dimension = "abi" + ndk { + abiFilters.add(defaultAbiList[index]) + } + } + } + } + } + + afterEvaluate { + for (profile in listOf("debug", "release")) { + val profileCapitalized = profile.replaceFirstChar { it.uppercase() } + val buildTask = tasks.maybeCreate( + "rustBuildUniversal$profileCapitalized", + DefaultTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for all targets" + } + + tasks["mergeUniversal${profileCapitalized}JniLibFolders"].dependsOn(buildTask) + + for (targetPair in targetsList.withIndex()) { + val targetName = targetPair.value + val targetArch = archList[targetPair.index] + val targetArchCapitalized = targetArch.replaceFirstChar { it.uppercase() } + val targetBuildTask = project.tasks.maybeCreate( + "rustBuild$targetArchCapitalized$profileCapitalized", + BuildTask::class.java + ).apply { + group = TASK_GROUP + description = "Build dynamic library in $profile mode for $targetArch" + rootDirRel = config.rootDirRel + target = targetName + release = profile == "release" + } + + buildTask.dependsOn(targetBuildTask) + tasks["merge$targetArchCapitalized${profileCapitalized}JniLibFolders"].dependsOn( + targetBuildTask + ) + } + } + } + } +} \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/gradle.properties b/mobile/src-tauri/gen/android/gradle.properties new file mode 100644 index 0000000..2a7ec69 --- /dev/null +++ b/mobile/src-tauri/gen/android/gradle.properties @@ -0,0 +1,24 @@ +# 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=-Xmx2048m -Dfile.encoding=UTF-8 +# 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 +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true +android.nonFinalResIds=false \ No newline at end of file diff --git a/mobile/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar b/mobile/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/mobile/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/mobile/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties b/mobile/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c5f9a53 --- /dev/null +++ b/mobile/src-tauri/gen/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Tue May 10 19:22:52 CST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/mobile/src-tauri/gen/android/gradlew b/mobile/src-tauri/gen/android/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/mobile/src-tauri/gen/android/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or 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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# 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" + which java >/dev/null 2>&1 || 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 + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/mobile/src-tauri/gen/android/gradlew.bat b/mobile/src-tauri/gen/android/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/mobile/src-tauri/gen/android/gradlew.bat @@ -0,0 +1,89 @@ +@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 + +@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=. +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%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/mobile/src-tauri/gen/android/settings.gradle b/mobile/src-tauri/gen/android/settings.gradle new file mode 100644 index 0000000..3939116 --- /dev/null +++ b/mobile/src-tauri/gen/android/settings.gradle @@ -0,0 +1,3 @@ +include ':app' + +apply from: 'tauri.settings.gradle' diff --git a/mobile/src-tauri/icons/128x128.png b/mobile/src-tauri/icons/128x128.png new file mode 100644 index 0000000..6be5e50 Binary files /dev/null and b/mobile/src-tauri/icons/128x128.png differ diff --git a/mobile/src-tauri/icons/128x128@2x.png b/mobile/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..e81bece Binary files /dev/null and b/mobile/src-tauri/icons/128x128@2x.png differ diff --git a/mobile/src-tauri/icons/32x32.png b/mobile/src-tauri/icons/32x32.png new file mode 100644 index 0000000..a437dd5 Binary files /dev/null and b/mobile/src-tauri/icons/32x32.png differ diff --git a/mobile/src-tauri/icons/Square107x107Logo.png b/mobile/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..0ca4f27 Binary files /dev/null and b/mobile/src-tauri/icons/Square107x107Logo.png differ diff --git a/mobile/src-tauri/icons/Square142x142Logo.png b/mobile/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..b81f820 Binary files /dev/null and b/mobile/src-tauri/icons/Square142x142Logo.png differ diff --git a/mobile/src-tauri/icons/Square150x150Logo.png b/mobile/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..624c7bf Binary files /dev/null and b/mobile/src-tauri/icons/Square150x150Logo.png differ diff --git a/mobile/src-tauri/icons/Square284x284Logo.png b/mobile/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..c021d2b Binary files /dev/null and b/mobile/src-tauri/icons/Square284x284Logo.png differ diff --git a/mobile/src-tauri/icons/Square30x30Logo.png b/mobile/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..6219700 Binary files /dev/null and b/mobile/src-tauri/icons/Square30x30Logo.png differ diff --git a/mobile/src-tauri/icons/Square310x310Logo.png b/mobile/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..f9bc048 Binary files /dev/null and b/mobile/src-tauri/icons/Square310x310Logo.png differ diff --git a/mobile/src-tauri/icons/Square44x44Logo.png b/mobile/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..d5fbfb2 Binary files /dev/null and b/mobile/src-tauri/icons/Square44x44Logo.png differ diff --git a/mobile/src-tauri/icons/Square71x71Logo.png b/mobile/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..63440d7 Binary files /dev/null and b/mobile/src-tauri/icons/Square71x71Logo.png differ diff --git a/mobile/src-tauri/icons/Square89x89Logo.png b/mobile/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..f3f705a Binary files /dev/null and b/mobile/src-tauri/icons/Square89x89Logo.png differ diff --git a/mobile/src-tauri/icons/StoreLogo.png b/mobile/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..4556388 Binary files /dev/null and b/mobile/src-tauri/icons/StoreLogo.png differ diff --git a/mobile/src-tauri/icons/icon.icns b/mobile/src-tauri/icons/icon.icns new file mode 100644 index 0000000..12a5bce Binary files /dev/null and b/mobile/src-tauri/icons/icon.icns differ diff --git a/mobile/src-tauri/icons/icon.ico b/mobile/src-tauri/icons/icon.ico new file mode 100644 index 0000000..b3636e4 Binary files /dev/null and b/mobile/src-tauri/icons/icon.ico differ diff --git a/mobile/src-tauri/icons/icon.png b/mobile/src-tauri/icons/icon.png new file mode 100644 index 0000000..e1cd261 Binary files /dev/null and b/mobile/src-tauri/icons/icon.png differ diff --git a/mobile/src-tauri/src/lib.rs b/mobile/src-tauri/src/lib.rs new file mode 100644 index 0000000..265ce67 --- /dev/null +++ b/mobile/src-tauri/src/lib.rs @@ -0,0 +1,9 @@ +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .plugin(tauri_plugin_opener::init()) + .plugin(tauri_plugin_shell::init()) + .plugin(tauri_plugin_deep_link::init()) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/mobile/src-tauri/src/main.rs b/mobile/src-tauri/src/main.rs new file mode 100644 index 0000000..9fe6433 --- /dev/null +++ b/mobile/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + family_budget_android_lib::run() +} diff --git a/mobile/src-tauri/tauri.conf.json b/mobile/src-tauri/tauri.conf.json new file mode 100644 index 0000000..44e322f --- /dev/null +++ b/mobile/src-tauri/tauri.conf.json @@ -0,0 +1,45 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "Family Budget", + "version": "0.1.0", + "identifier": "com.arrelin.family-budget-android", + "build": { + "beforeDevCommand": "", + "devUrl": "https://family-budget.duckdns.org", + "beforeBuildCommand": "npm run build --prefix ../frontend", + "frontendDist": "../frontend/dist" + }, + "app": { + "windows": [ + { + "title": "family-budget-android", + "width": 800, + "height": 600 + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + }, + "plugins": { + "deep-link": { + "mobile": [ + { + "scheme": "com.arrelin.family-budget-android", + "host": "auth" + } + ] + } + } +}