diff --git a/.gitignore b/.gitignore index 12bc7fa..5afed13 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ Thumbs.db # Replit .cache/ .local/ +scripts/push-to-gitea.sh diff --git a/README.md b/README.md index a3081ec..41cb70d 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,8 @@ # PostizMobile -React Native (Expo) mobile app to control a self-hosted **Postiz** instance from your Android or iOS device. +React Native (Expo) mobile app to control a self-hosted [Postiz](https://postiz.com) instance from Android. ---- - -## Table of Contents - -1. [Features](#features) -2. [Prerequisites](#prerequisites) -3. [Installation & Development](#installation--development) -4. [App Configuration](#app-configuration) -5. [Project Architecture](#project-architecture) -6. [Postiz API](#postiz-api) -7. [Android APK Build (EAS)](#android-apk-build-eas) -8. [iOS Build (Expo Launch)](#ios-build-expo-launch) -9. [Pushing Changes to Gitea](#pushing-changes-to-gitea) -10. [Environment Variables & Secrets](#environment-variables--secrets) -11. [Troubleshooting](#troubleshooting) +Build is fully local — no expo.dev account or EAS cloud required. --- @@ -25,357 +11,100 @@ React Native (Expo) mobile app to control a self-hosted **Postiz** instance from | Screen | Description | |--------|-------------| | **Calendar** | Monthly view with color dots per day (indigo = scheduled, green = published, red = error). Tap a day to see its posts. | -| **Posts** | Filtered list (All / Queue / Published / Draft / Error) with pull-to-refresh and swipe left to delete. | -| **Compose** | Text editor, channel picker, date/time picker, gallery image import + upload, publish now or schedule. | -| **Settings** | API key and base URL input, connection test, secure storage (SecureStore). | -| **Notifications** | Automatic local alerts when a post transitions to PUBLISHED or ERROR (polling every 15 minutes). | +| **Posts** | Filtered list (All / Queue / Published / Draft / Error) with sort toggle, pull-to-refresh, swipe left to delete, swipe right to reschedule. | +| **Compose** | Text editor with per-network character limit, channel picker, date/time picker, gallery image pick + upload, publish now or schedule. Local draft save/restore. | +| **Settings** | API key and base URL, connection test, secure storage. 401 auto-redirect to Settings. | +| **Notifications** | Local alerts when a post transitions to PUBLISHED or ERROR (polling every 15 min). | -**Theme**: forced dark (`userInterfaceStyle: dark`). -**Authentication**: API key stored in `expo-secure-store`, never hardcoded. +**Theme**: forced dark. **Auth**: API key in `expo-secure-store`, never hardcoded. --- ## Prerequisites -| Tool | Minimum version | -|------|----------------| +| Tool | Version | +|------|---------| | Node.js | 20 LTS | | pnpm | 10+ | -| Expo Go (phone) | SDK 54 compatible | -| EAS account (for APK) | free on expo.dev | - -```bash -npm install -g pnpm -npm install -g eas-cli -``` +| Java (JDK) | 17–24 (Java 25+ not yet supported by Gradle 8) | +| Android SDK | see below | --- ## Installation & Development -### 1. Clone the repository - -```bash -git clone ssh://gitea@homegit.gyozamancave.fr:2222/billisdead/Postiz-android.git -cd Postiz-android -``` - -### 2. Install dependencies - ```bash +git clone https://github.com/pirona/postiz-android.git +cd postiz-android pnpm install ``` -### 3. Start the development server +Start the dev server (requires Expo Go on the device): ```bash pnpm --filter @workspace/postiz-mobile run dev ``` -The terminal displays a QR code. Scan it with **Expo Go** (Android) or the **Camera** app (iOS) to see the app live. +--- -### 4. Open in the browser (web preview) +## Building an APK (local, no EAS) -``` -http://localhost: +See **[artifacts/postiz-mobile/README.md](artifacts/postiz-mobile/README.md)** for the full build guide. + +Quick start: + +```bash +cd artifacts/postiz-mobile +./install-android-sdk.sh # first time only +cp ~/.config/postiz-mobile/signing.env.example ~/.config/postiz-mobile/signing.env +$EDITOR ~/.config/postiz-mobile/signing.env # fill in keystore credentials +./build-apk.sh # → dist/postiz-mobile-YYYYMMDD-HHMM.apk ``` -The port is dynamically assigned by the Replit environment. +### GitHub Actions release + +Pushing a tag triggers an automated signed APK release: + +```bash +git tag v1.0.0 +git push origin --tags +``` + +The workflow builds the APK on GitHub's infrastructure and attaches it to a GitHub Release. +Required secrets: `KEYSTORE_B64`, `KEYSTORE_ALIAS`, `KEYSTORE_STORE_PASSWORD`, `KEYSTORE_KEY_PASSWORD`. --- ## App Configuration -On first launch, the **Settings** screen is shown because no key is configured yet. +On first launch, go to **Settings**: -1. **Base URL**: `https://your-postiz-instance.fr/public/v1` -2. **API Key**: generated from your Postiz instance → *Settings → API Keys* -3. Tap **Test Connection** to validate -4. Tap **Save Settings** - -The key is encrypted and stored locally via `expo-secure-store`. It is never sent to a third-party service. - ---- - -## Project Architecture - -``` -artifacts/postiz-mobile/ -├── app/ -│ ├── _layout.tsx # Root layout : providers, fonts, notifications -│ └── (tabs)/ -│ ├── _layout.tsx # Tab bar (NativeTabs iOS 26+ / Tabs classique) -│ ├── index.tsx # Écran Calendrier -│ ├── posts.tsx # Écran Liste des posts -│ ├── compose.tsx # Écran Composer -│ └── settings.tsx # Écran Paramètres -├── components/ -│ ├── ChannelChip.tsx # Chip de sélection de canal -│ ├── ErrorBoundary.tsx # Gestionnaire d'erreurs global -│ ├── PostCard.tsx # Carte post avec swipe-to-delete -│ └── StatusBadge.tsx # Badge QUEUE / PUBLISHED / ERROR / DRAFT -├── constants/ -│ └── colors.ts # Palette dark theme -├── context/ -│ └── PostizContext.tsx # Client axios + SecureStore (apiKey, baseUrl) -├── hooks/ -│ ├── useColors.ts # Tokens couleur selon le thème -│ └── useNotifications.ts # Permissions + polling + notifications locales -├── assets/ -│ └── images/ -│ └── icon.png # Icône générée par IA -└── app.json # Config Expo (permissions, plugins, thème) -``` - -### Main dependencies - -| Package | Usage | -|---------|-------| -| `expo-router` | File-based navigation | -| `axios` | HTTP client for the Postiz API | -| `expo-secure-store` | Encrypted API key storage | -| `react-native-calendars` | Monthly calendar view | -| `@react-native-community/datetimepicker` | Date/time picker in Compose | -| `expo-image-picker` | Gallery photo access | -| `expo-notifications` | Local status notifications | -| `expo-task-manager` | Background task for polling | -| `@tanstack/react-query` | API data cache and refetch | +1. **Base URL**: `https://your-postiz-instance/api/public/v1` +2. **API Key**: generated in Postiz → Settings → API Keys +3. Tap **Test Connection**, then **Save Settings** --- ## Postiz API -Base URL configured by the user (e.g. `https://postiz.example.com/public/v1`). - | Method | Endpoint | Usage | |--------|----------|-------| -| `GET` | `/integrations` | List channels (Twitter, LinkedIn, etc.) | +| `GET` | `/integrations` | List channels | | `GET` | `/posts?startDate=&endDate=` | Posts over a date range | | `POST` | `/posts` | Create / schedule a post | | `DELETE` | `/posts/:id` | Delete a post | | `POST` | `/upload` | Upload an image (multipart) | -### POST /posts payload example - -```json -{ - "type": "schedule", - "date": "2025-01-15T10:00:00.000Z", - "content": [ - { - "content": "Mon super post 🚀", - "image": [{ "id": "upload-id", "path": "/uploads/photo.jpg" }] - } - ], - "integrations": ["integration-id-twitter", "integration-id-linkedin"] -} -``` - -To publish immediately, use `"type": "now"`. - ---- - -## Android APK Build (EAS) - -> **Prerequisites**: free account on [expo.dev](https://expo.dev) and `eas-cli` installed. - -### 1. Log in to EAS - -```bash -npx eas login -``` - -### 2. Initialize EAS in the project - -```bash -cd artifacts/postiz-mobile -npx eas init -``` - -This generates a `projectId` in `app.json`. - -### 3. EAS configuration file - -> **Already included in the repository**: `artifacts/postiz-mobile/eas.json` is present, you can skip this step. - -To recreate it manually: - -```json -{ - "cli": { - "version": ">= 16.0.0" - }, - "build": { - "preview": { - "android": { - "buildType": "apk" - } - }, - "production": { - "android": { - "buildType": "app-bundle" - } - } - }, - "submit": { - "production": {} - } -} -``` - -### 4. Start the APK build - -```bash -# APK de test (sideload) -npx eas build --platform android --profile preview - -# AAB pour le Play Store -npx eas build --platform android --profile production -``` - -The build runs in the EAS cloud (~10-15 min). At the end, EAS displays a **download link** and a **QR code** to retrieve the `.apk` file. - -### 5. Install the APK on your phone - -```bash -# Via adb -adb install postiz-mobile.apk - -# Ou scannez le QR code affiché par EAS -``` - -### 6. Publish the APK as a Gitea Release - -Once the `.apk` is downloaded from EAS, attach it to a Gitea release to make it available directly from the repository: - -1. Go to `https://homegit.gyozamancave.fr/billisdead/Postiz-android/releases` -2. Click **New Release** -3. Choose a tag (e.g. `v1.0.0`) and a title -4. Drag and drop the `.apk` file into the attachments area -5. Click **Publish Release** - -The APK will then be downloadable directly from the Gitea repository page. - -### Declared Android permissions - -```xml -READ_EXTERNAL_STORAGE -WRITE_EXTERNAL_STORAGE -READ_MEDIA_IMAGES -RECEIVE_BOOT_COMPLETED -VIBRATE -``` - ---- - -## iOS Build (Expo Launch) - -> Available only via **Replit Expo Launch** (automated App Store submission). - -1. In Replit, click the **Publish** button -2. Select **Expo Launch** -3. Follow the wizard (Apple Developer account required) - -**Note**: Google Play publishing is not yet supported by Expo Launch — use EAS for Android. - ---- - -## Pushing Changes to Gitea - -The remote repository is: `ssh://gitea@homegit.gyozamancave.fr:2222/billisdead/Postiz-android.git` - -The SSH key used is stored in the `GITEA_SSH_KEY` environment variable (on the Replit side). - -### Push from your local machine - -```bash -# Ajouter le remote (une seule fois) -git remote add gitea ssh://gitea@homegit.gyozamancave.fr:2222/billisdead/Postiz-android.git - -# Pousser -git push gitea main -``` - -Make sure your public SSH key is added in Gitea → *User Settings → SSH / GPG Keys*. - -### Push from Replit (via script) - -From Replit, direct `git push` commands are restricted. Use the bundle script: - -```bash -# Créer le bundle -git bundle create /tmp/postiz.bundle main - -# Cloner le bundle et pousser -git clone /tmp/postiz.bundle /tmp/repo_push -cd /tmp/repo_push -git remote add gitea ssh://gitea@homegit.gyozamancave.fr:2222/billisdead/Postiz-android.git -GIT_SSH_COMMAND="ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=no -p 2222" \ - git push --force gitea main -``` - ---- - -## Environment Variables & Secrets - -| Variable | Storage | Description | -|----------|---------|-------------| -| `GITEA_SSH_KEY` | Replit Secrets (shared) | Private SSH key for pushing to Gitea | -| `SESSION_SECRET` | Replit Secrets | Session secret (API server) | - -App-side variables (Postiz API key, URL) are **entered by the user** in the Settings screen and stored in `expo-secure-store` — they never pass through the source code. - --- ## Troubleshooting -### The app shows "Not Configured" on all screens +**"Not Configured" on all screens** → Settings tab → enter API key and URL → Test Connection. -→ Go to the **Settings** tab, enter your API key and URL, then tap **Test Connection**. +**"Connection failed"** → URL must end with `/api/public/v1` — check Postiz is reachable. -### "Connection failed" in Settings +**No notifications** → Accept permissions on first launch. Polling runs every 15 min. -- Check that the URL ends with `/public/v1` -- Check that the API key is valid (generated in Postiz → API Keys) -- Check that your Postiz instance is accessible from the internet +**Build fails at Gradle** → Make sure `ANDROID_HOME` is set and Java is ≤ 24 (the script auto-detects `~/jdk21`). -### No notifications received - -- Accept notification permissions on first launch -- Polling runs every 15 minutes — wait for a full cycle -- On Android, check that the app's notifications are not disabled in system settings - -### Metro error "module not found" - -```bash -pnpm install -# Puis redémarrer le workflow Expo -``` - -### Calendar does not load posts - -- Check that the Postiz API supports `startDate` / `endDate` parameters on `GET /posts` -- Check network logs: in Expo Go, shake the device → *Open Debugger* - -### EAS build fails - -```bash -# Vérifier la version Expo -npx expo --version - -# Vérifier la cohérence des packages -npx expo install --check -``` - ---- - -## Contributing - -1. Fork on Gitea: `https://homegit.gyozamancave.fr/billisdead/Postiz-android` -2. Create a feature branch: `git checkout -b feature/my-feature` -3. Commit your changes -4. Push and open a Pull Request - ---- - -*Generated with ❤️ on Replit — PostizMobile v1.0.0* +**`expo prebuild` fails** → Run `pnpm install` from the repo root first. diff --git a/artifacts/postiz-mobile/README.md b/artifacts/postiz-mobile/README.md index b9d7c7e..8098921 100644 --- a/artifacts/postiz-mobile/README.md +++ b/artifacts/postiz-mobile/README.md @@ -38,8 +38,8 @@ No expo.dev account needed for builds. ### Install dependencies ```bash -git clone ssh://gitea@homegit.gyozamancave.fr:2222/billisdead/Postiz-android.git -cd Postiz-android +git clone https://github.com/pirona/postiz-android.git +cd postiz-android pnpm install ``` diff --git a/artifacts/postiz-mobile/package.json b/artifacts/postiz-mobile/package.json index cea67bd..7e9d2a1 100644 --- a/artifacts/postiz-mobile/package.json +++ b/artifacts/postiz-mobile/package.json @@ -4,7 +4,7 @@ "private": true, "main": "expo-router/entry", "scripts": { - "dev": "EXPO_PACKAGER_PROXY_URL=https://$REPLIT_EXPO_DEV_DOMAIN EXPO_PUBLIC_DOMAIN=$REPLIT_DEV_DOMAIN EXPO_PUBLIC_REPL_ID=$REPL_ID REACT_NATIVE_PACKAGER_HOSTNAME=$REPLIT_DEV_DOMAIN pnpm exec expo start --localhost --port $PORT", + "dev": "pnpm exec expo start", "build": "node scripts/build.js", "serve": "node server/serve.js", "typecheck": "tsc -p tsconfig.json --noEmit", diff --git a/artifacts/postiz-mobile/scripts/build.js b/artifacts/postiz-mobile/scripts/build.js index 6cce2d1..3343a6f 100644 --- a/artifacts/postiz-mobile/scripts/build.js +++ b/artifacts/postiz-mobile/scripts/build.js @@ -55,20 +55,12 @@ function stripProtocol(domain) { } function getDeploymentDomain() { - if (process.env.REPLIT_INTERNAL_APP_DOMAIN) { - return stripProtocol(process.env.REPLIT_INTERNAL_APP_DOMAIN); - } - - if (process.env.REPLIT_DEV_DOMAIN) { - return stripProtocol(process.env.REPLIT_DEV_DOMAIN); - } - if (process.env.EXPO_PUBLIC_DOMAIN) { return stripProtocol(process.env.EXPO_PUBLIC_DOMAIN); } console.error( - "ERROR: No deployment domain found. Set REPLIT_INTERNAL_APP_DOMAIN, REPLIT_DEV_DOMAIN, or EXPO_PUBLIC_DOMAIN", + "ERROR: No deployment domain found. Set EXPO_PUBLIC_DOMAIN.", ); process.exit(1); } @@ -124,7 +116,7 @@ async function checkMetroHealth() { } function getExpoPublicReplId() { - return process.env.REPL_ID || process.env.EXPO_PUBLIC_REPL_ID; + return process.env.EXPO_PUBLIC_REPL_ID; } async function startMetro(expoPublicDomain, expoPublicReplId) { diff --git a/replit.md b/replit.md deleted file mode 100644 index 90dc480..0000000 --- a/replit.md +++ /dev/null @@ -1,64 +0,0 @@ -# Workspace - -## Overview - -pnpm workspace monorepo using TypeScript. Each package manages its own dependencies. - -## Stack - -- **Monorepo tool**: pnpm workspaces -- **Node.js version**: 24 -- **Package manager**: pnpm -- **TypeScript version**: 5.9 -- **API framework**: Express 5 -- **Database**: PostgreSQL + Drizzle ORM -- **Validation**: Zod (`zod/v4`), `drizzle-zod` -- **API codegen**: Orval (from OpenAPI spec) -- **Build**: esbuild (CJS bundle) - -## Artifacts - -### PostizMobile (`artifacts/postiz-mobile`) -Expo (React Native) mobile client for a self-hosted Postiz instance. - -- **Preview path**: `/` -- **Theme**: Dark-only (`userInterfaceStyle: dark`) -- **Auth**: API key stored in `expo-secure-store`, passed as `Authorization` header - -#### Screens / Tabs -1. **Calendar** (`app/(tabs)/index.tsx`) — Monthly calendar with post dots, tap day to see posts -2. **Posts** (`app/(tabs)/posts.tsx`) — Filterable list of posts with status badges, swipe to delete -3. **Compose** (`app/(tabs)/compose.tsx`) — Text editor, channel picker, date/time picker, image upload -4. **Settings** (`app/(tabs)/settings.tsx`) — API key + base URL, validation, SecureStore persistence - -#### Key Files -- `context/PostizContext.tsx` — Axios client wired with API key/base URL; loaded from SecureStore on boot -- `components/PostCard.tsx` — Swipeable post card with delete action -- `components/StatusBadge.tsx` — QUEUE / PUBLISHED / ERROR / DRAFT badges -- `components/ChannelChip.tsx` — Integration channel selector chip - -#### External API -- Base URL: `https://postiz.gyozamancave.fr/public/v1` (configurable) -- `GET /integrations` — List channels -- `GET /posts?startDate&endDate` — List posts -- `POST /posts` — Create/schedule post -- `DELETE /posts/:id` — Delete post -- `POST /upload` — Upload media - -#### Packages Added -- `axios` — HTTP client -- `expo-secure-store` — Secure API key storage -- `react-native-calendars` — Calendar UI -- `@react-native-community/datetimepicker` — Date/time picker for compose - -### API Server (`artifacts/api-server`) -Express 5 backend. Currently serves `/api/healthz`. Extend for server-side features. - -## Key Commands - -- `pnpm run typecheck` — full typecheck across all packages -- `pnpm run build` — typecheck + build all packages -- `pnpm --filter @workspace/api-spec run codegen` — regenerate API hooks and Zod schemas from OpenAPI spec -- `pnpm --filter @workspace/db run push` — push DB schema changes (dev only) - -See the `pnpm-workspace` skill for workspace structure, TypeScript setup, and package details. diff --git a/scripts/push-to-gitea.sh b/scripts/push-to-gitea.sh deleted file mode 100755 index 7a4ffc1..0000000 --- a/scripts/push-to-gitea.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -set -euo pipefail - -GITEA_HOST="homegit.gyozamancave.fr" -GITEA_USER="billisdead" -GITEA_REPO="Postiz-android" -GITEA_REMOTE_NAME="gitea" -GITEA_REMOTE_URL="https://${GITEA_HOST}/${GITEA_USER}/${GITEA_REPO}.git" - -if [ -z "${GITEA_SSH_KEY:-}" ]; then - echo "Error: GITEA_SSH_KEY environment variable is not set. Add the Gitea API token as a Replit secret named GITEA_SSH_KEY." >&2 - exit 1 -fi - -if git remote get-url "$GITEA_REMOTE_NAME" &>/dev/null; then - git remote set-url "$GITEA_REMOTE_NAME" "$GITEA_REMOTE_URL" -else - git remote add "$GITEA_REMOTE_NAME" "$GITEA_REMOTE_URL" - echo "Added remote '$GITEA_REMOTE_NAME'." -fi - -echo "Pushing main branch to Gitea..." -git -c "http.extraHeader=Authorization: token ${GITEA_SSH_KEY}" \ - push --force "$GITEA_REMOTE_NAME" main -echo "Push to Gitea complete."