Agent skill
add-capacitor-android-tassadar2499-novatune
Add Capacitor wrapper for NovaTune Player Android app with secure storage (project)
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/add-capacitor-android-tassadar2499-novatune
SKILL.md
Add Capacitor Android Skill
Package the NovaTune Player as an Android application using Capacitor with secure token storage.
Overview
This skill adds Capacitor to apps/player for:
- Android app builds
- Secure refresh token storage
- Native Android integration
- Deep linking support
Project Structure
apps/player/
├── capacitor.config.ts
├── android/ (generated)
│ ├── app/
│ │ ├── src/
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── res/
│ │ └── build.gradle
│ └── build.gradle
├── src/
│ └── ... (Vue app source)
└── dist/ (built web app)
Capacitor Configuration
capacitor.config.ts
import type { CapacitorConfig } from '@capacitor/cli';
const config: CapacitorConfig = {
appId: 'dev.novatune.player',
appName: 'NovaTune',
webDir: 'dist',
server: {
androidScheme: 'https',
// For development only:
// url: 'http://10.0.2.2:5173',
// cleartext: true,
},
plugins: {
SecureStoragePlugin: {
// Uses Android Keystore by default
},
SplashScreen: {
launchShowDuration: 2000,
backgroundColor: '#1a1a1a',
showSpinner: false,
},
StatusBar: {
style: 'dark',
backgroundColor: '#1a1a1a',
},
},
android: {
allowMixedContent: false,
webContentsDebuggingEnabled: false, // Set to true for development
},
};
export default config;
Setup Steps
1. Install Capacitor
cd apps/player
# Core Capacitor packages
pnpm add @capacitor/core
pnpm add -D @capacitor/cli
# Initialize Capacitor
npx cap init NovaTune dev.novatune.player --web-dir=dist
# Add Android platform
pnpm add @capacitor/android
npx cap add android
2. Install Plugins
# Secure storage for tokens
pnpm add capacitor-secure-storage-plugin
# Optional: native features
pnpm add @capacitor/status-bar
pnpm add @capacitor/splash-screen
pnpm add @capacitor/app
Secure Storage Integration
packages/core/src/auth/storage-capacitor.ts
import { SecureStoragePlugin } from 'capacitor-secure-storage-plugin';
const REFRESH_TOKEN_KEY = 'refresh_token';
export const capacitorStorage = {
async getRefreshToken(): Promise<string | null> {
try {
const { value } = await SecureStoragePlugin.get({ key: REFRESH_TOKEN_KEY });
return value;
} catch (error) {
// Key not found returns error
return null;
}
},
async setRefreshToken(token: string): Promise<void> {
await SecureStoragePlugin.set({
key: REFRESH_TOKEN_KEY,
value: token,
});
},
async clearRefreshToken(): Promise<void> {
try {
await SecureStoragePlugin.remove({ key: REFRESH_TOKEN_KEY });
} catch {
// Ignore if key doesn't exist
}
},
};
packages/core/src/auth/storage.ts
import { Capacitor } from '@capacitor/core';
export interface TokenStorage {
getRefreshToken(): Promise<string | null>;
setRefreshToken(token: string): Promise<void>;
clearRefreshToken(): Promise<void>;
}
export async function createTokenStorage(): Promise<TokenStorage> {
// Native mobile (Capacitor)
if (Capacitor.isNativePlatform()) {
const { capacitorStorage } = await import('./storage-capacitor');
return capacitorStorage;
}
// Electron
if (typeof window !== 'undefined' && window.electronAPI) {
return {
getRefreshToken: () => window.electronAPI.getSecureToken(),
setRefreshToken: (token) => window.electronAPI.setSecureToken(token),
clearRefreshToken: () => window.electronAPI.deleteSecureToken(),
};
}
// Web fallback
return {
getRefreshToken: () => Promise.resolve(localStorage.getItem('refresh_token')),
setRefreshToken: (token) => {
localStorage.setItem('refresh_token', token);
return Promise.resolve();
},
clearRefreshToken: () => {
localStorage.removeItem('refresh_token');
return Promise.resolve();
},
};
}
Android Manifest Updates
android/app/src/main/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="false">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:exported="true"
android:launchMode="singleTask"
android:name=".MainActivity"
android:theme="@style/AppTheme.NoActionBarLaunch">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Deep linking -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="novatune.dev" />
</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" />
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
</manifest>
Build Workflow
Development
# Build web app
pnpm build
# Sync to Android project
npx cap sync android
# Open in Android Studio
npx cap open android
# Or run directly
npx cap run android
Production Build
# Build optimized web app
pnpm build
# Sync to Android
npx cap sync android
# Build APK/AAB in Android Studio
# or use Gradle directly:
cd android
./gradlew assembleRelease # APK
./gradlew bundleRelease # AAB for Play Store
Scripts Addition
apps/player/package.json
{
"scripts": {
"android:sync": "cap sync android",
"android:open": "cap open android",
"android:run": "cap run android",
"android:build": "pnpm build && cap sync android"
}
}
Network Configuration
android/app/src/main/res/xml/network_security_config.xml
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
<!-- Development only -->
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
<domain includeSubdomains="true">localhost</domain>
</domain-config>
</network-security-config>
Splash Screen
android/app/src/main/res/values/styles.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@color/splash_background</item>
</style>
</resources>
android/app/src/main/res/values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6366f1</color>
<color name="colorPrimaryDark">#4f46e5</color>
<color name="colorAccent">#8b5cf6</color>
<color name="splash_background">#1a1a1a</color>
</resources>
Background Audio (Future Enhancement)
For background audio playback, consider adding:
// Future: Background audio service
import { App } from '@capacitor/app';
App.addListener('appStateChange', ({ isActive }) => {
if (!isActive) {
// App went to background
// Audio continues via native service
}
});
Implementation Checklist
- Initialize Capacitor in apps/player
- Add Android platform
- Install secure storage plugin
- Update token storage to use platform detection
- Configure Android Manifest
- Add network security config
- Create app icons and splash screen
- Test secure storage on device
- Test audio playback
- Configure release signing
Related Documentation
- Frontend Plan:
doc/implementation/frontend/main.md - Section 13: Android (Capacitor) Packaging
Related Skills
- implement-player-app - Base player app
- add-electron-wrapper - Desktop packaging
Claude Agents
- vue-app-implementer - Base app implementation
- capacitor-android-implementer - Android-specific tasks
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?