SilverDROID - Dark Side Admin with CI/CD pipeline
- Android PWA/WASM launcher with glassmorphism UI - Loads https://admin.dark.side directly on launch - Complete GitLab CI/CD pipeline configuration - Automated builds for Debug, Release, and AAB - Full WASM support with optimized WebView - Material Design 3 theme - Comprehensive documentation Features: - Auto-load target URL on app launch - Glassmorphism components (frosted glass effects) - Full PWA/WASM support - Room database for future extensions - Jetpack Compose UI - CI/CD with artifact storage Built for SilverLABS
This commit is contained in:
82
.gitignore
vendored
Normal file
82
.gitignore
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
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/
|
||||
misc.xml
|
||||
deploymentTargetDropDown.xml
|
||||
render.experimental.xml
|
||||
|
||||
# Keystore files
|
||||
*.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-baseline.xml
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
264
.gitlab-ci.yml
Normal file
264
.gitlab-ci.yml
Normal file
@@ -0,0 +1,264 @@
|
||||
# GitLab CI/CD Pipeline for SilverDROID (Dark Side Admin)
|
||||
# Android APK Build & Deployment
|
||||
|
||||
image: mingc/android-build-box:latest
|
||||
|
||||
variables:
|
||||
ANDROID_COMPILE_SDK: "35"
|
||||
ANDROID_BUILD_TOOLS: "34.0.0"
|
||||
ANDROID_SDK_TOOLS: "9477386"
|
||||
GRADLE_OPTS: "-Dorg.gradle.daemon=false -Dorg.gradle.caching=true"
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
stages:
|
||||
- prepare
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
before_script:
|
||||
- export GRADLE_USER_HOME=$(pwd)/.gradle
|
||||
- chmod +x ./gradlew
|
||||
|
||||
# Cache Gradle dependencies
|
||||
cache:
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- .gradle/wrapper
|
||||
- .gradle/caches
|
||||
- build/
|
||||
- app/build/
|
||||
|
||||
# ===========================
|
||||
# STAGE: Prepare
|
||||
# ===========================
|
||||
|
||||
prepare:dependencies:
|
||||
stage: prepare
|
||||
script:
|
||||
- echo "Downloading Gradle dependencies..."
|
||||
- ./gradlew --version
|
||||
- ./gradlew dependencies
|
||||
cache:
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- .gradle/
|
||||
policy: pull-push
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
- merge_requests
|
||||
|
||||
# ===========================
|
||||
# STAGE: Test
|
||||
# ===========================
|
||||
|
||||
lint:
|
||||
stage: test
|
||||
script:
|
||||
- echo "Running Android Lint..."
|
||||
- ./gradlew lint
|
||||
artifacts:
|
||||
name: "lint-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app/build/reports/lint-results*.html
|
||||
- app/build/reports/lint-results*.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
- merge_requests
|
||||
|
||||
unit_tests:
|
||||
stage: test
|
||||
script:
|
||||
- echo "Running unit tests..."
|
||||
- ./gradlew test
|
||||
artifacts:
|
||||
name: "tests-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app/build/reports/tests/
|
||||
reports:
|
||||
junit: app/build/test-results/test*UnitTest/**.xml
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
- merge_requests
|
||||
|
||||
# ===========================
|
||||
# STAGE: Build
|
||||
# ===========================
|
||||
|
||||
build:debug:
|
||||
stage: build
|
||||
script:
|
||||
- echo "Building Debug APK..."
|
||||
- ./gradlew assembleDebug
|
||||
- echo "APK built successfully!"
|
||||
- ls -lh app/build/outputs/apk/debug/
|
||||
artifacts:
|
||||
name: "silverdroid-debug-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app/build/outputs/apk/debug/app-debug.apk
|
||||
expire_in: 30 days
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
- merge_requests
|
||||
- tags
|
||||
|
||||
build:release:
|
||||
stage: build
|
||||
script:
|
||||
- echo "Building Release APK..."
|
||||
- ./gradlew assembleRelease
|
||||
- echo "Release APK built successfully!"
|
||||
- ls -lh app/build/outputs/apk/release/
|
||||
artifacts:
|
||||
name: "silverdroid-release-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app/build/outputs/apk/release/app-release-unsigned.apk
|
||||
expire_in: 90 days
|
||||
only:
|
||||
- main
|
||||
- tags
|
||||
|
||||
build:bundle:
|
||||
stage: build
|
||||
script:
|
||||
- echo "Building Android App Bundle (AAB)..."
|
||||
- ./gradlew bundleRelease
|
||||
- echo "AAB built successfully!"
|
||||
- ls -lh app/build/outputs/bundle/release/
|
||||
artifacts:
|
||||
name: "silverdroid-bundle-${CI_COMMIT_SHORT_SHA}"
|
||||
paths:
|
||||
- app/build/outputs/bundle/release/app-release.aab
|
||||
expire_in: 90 days
|
||||
only:
|
||||
- main
|
||||
- tags
|
||||
|
||||
# ===========================
|
||||
# STAGE: Deploy
|
||||
# ===========================
|
||||
|
||||
deploy:staging:
|
||||
stage: deploy
|
||||
script:
|
||||
- echo "Deploying to staging environment..."
|
||||
- echo "APK available at: ${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/download"
|
||||
- |
|
||||
curl -X POST "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/statuses/${CI_COMMIT_SHA}" \
|
||||
--header "PRIVATE-TOKEN: ${CI_JOB_TOKEN}" \
|
||||
--data "state=success" \
|
||||
--data "name=APK Build" \
|
||||
--data "target_url=${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/download"
|
||||
dependencies:
|
||||
- build:debug
|
||||
environment:
|
||||
name: staging
|
||||
url: https://gitlab.silverlabs.uk/${CI_PROJECT_PATH}/-/jobs/${CI_JOB_ID}/artifacts/download
|
||||
only:
|
||||
- develop
|
||||
|
||||
deploy:production:
|
||||
stage: deploy
|
||||
script:
|
||||
- echo "Deploying to production..."
|
||||
- echo "Release APK: app/build/outputs/apk/release/app-release-unsigned.apk"
|
||||
- echo "Creating release tag..."
|
||||
dependencies:
|
||||
- build:release
|
||||
environment:
|
||||
name: production
|
||||
url: https://gitlab.silverlabs.uk/${CI_PROJECT_PATH}/-/releases
|
||||
only:
|
||||
- tags
|
||||
when: manual
|
||||
|
||||
# ===========================
|
||||
# Additional Jobs
|
||||
# ===========================
|
||||
|
||||
# Security scan
|
||||
security:scan:
|
||||
stage: test
|
||||
script:
|
||||
- echo "Running security scan..."
|
||||
- ./gradlew dependencyCheckAnalyze || true
|
||||
artifacts:
|
||||
paths:
|
||||
- build/reports/dependency-check-report.html
|
||||
expire_in: 1 week
|
||||
when: always
|
||||
allow_failure: true
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
|
||||
# Generate APK info
|
||||
apk:info:
|
||||
stage: deploy
|
||||
script:
|
||||
- echo "Extracting APK information..."
|
||||
- |
|
||||
APK_SIZE=$(du -h app/build/outputs/apk/debug/app-debug.apk | cut -f1)
|
||||
echo "APK Size: $APK_SIZE"
|
||||
echo "Commit: ${CI_COMMIT_SHORT_SHA}"
|
||||
echo "Branch: ${CI_COMMIT_REF_NAME}"
|
||||
echo "Build Time: $(date)"
|
||||
echo "---" > apk-info.txt
|
||||
echo "SilverDROID - Dark Side Admin" >> apk-info.txt
|
||||
echo "Build: ${CI_COMMIT_SHORT_SHA}" >> apk-info.txt
|
||||
echo "Size: $APK_SIZE" >> apk-info.txt
|
||||
echo "Date: $(date)" >> apk-info.txt
|
||||
dependencies:
|
||||
- build:debug
|
||||
artifacts:
|
||||
paths:
|
||||
- apk-info.txt
|
||||
expire_in: 30 days
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
|
||||
# Notification (optional - requires webhook setup)
|
||||
notify:success:
|
||||
stage: deploy
|
||||
script:
|
||||
- echo "Build successful! Sending notification..."
|
||||
- |
|
||||
curl -X POST "https://chat.silverlabs.uk/webhook" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"text\": \"✅ SilverDROID Build Successful\",
|
||||
\"commit\": \"${CI_COMMIT_SHORT_SHA}\",
|
||||
\"branch\": \"${CI_COMMIT_REF_NAME}\",
|
||||
\"pipeline\": \"${CI_PIPELINE_URL}\"
|
||||
}" || true
|
||||
when: on_success
|
||||
allow_failure: true
|
||||
only:
|
||||
- main
|
||||
|
||||
notify:failure:
|
||||
stage: deploy
|
||||
script:
|
||||
- echo "Build failed! Sending notification..."
|
||||
- |
|
||||
curl -X POST "https://chat.silverlabs.uk/webhook" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"text\": \"❌ SilverDROID Build Failed\",
|
||||
\"commit\": \"${CI_COMMIT_SHORT_SHA}\",
|
||||
\"branch\": \"${CI_COMMIT_REF_NAME}\",
|
||||
\"pipeline\": \"${CI_PIPELINE_URL}\"
|
||||
}" || true
|
||||
when: on_failure
|
||||
allow_failure: true
|
||||
only:
|
||||
- main
|
||||
170
BUILD.md
Normal file
170
BUILD.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# Build Instructions for SilverDROID
|
||||
|
||||
## Quick Start (Windows)
|
||||
|
||||
### From WSL/Linux:
|
||||
```bash
|
||||
cd /mnt/c/Production/Source/SilverLABS/SilverDROID
|
||||
|
||||
# Build debug APK
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Install on connected device
|
||||
./gradlew installDebug
|
||||
```
|
||||
|
||||
### From Windows PowerShell:
|
||||
```powershell
|
||||
cd C:\Production\Source\SilverLABS\SilverDROID
|
||||
|
||||
# Build debug APK
|
||||
.\gradlew.bat assembleDebug
|
||||
|
||||
# Install on connected device
|
||||
.\gradlew.bat installDebug
|
||||
```
|
||||
|
||||
## Android Studio Setup
|
||||
|
||||
1. **Open Project**
|
||||
- Launch Android Studio
|
||||
- File → Open → Navigate to `SilverDROID` folder
|
||||
- Wait for Gradle sync to complete
|
||||
|
||||
2. **Connect Device or Emulator**
|
||||
- Physical Device: Enable USB debugging in Developer Options
|
||||
- Emulator: Create an AVD with Android 8.0+ (API 26+)
|
||||
|
||||
3. **Run the App**
|
||||
- Click the green ▶️ (Run) button
|
||||
- Select your device/emulator
|
||||
- Wait for build and install
|
||||
|
||||
## Build Variants
|
||||
|
||||
### Debug Build (Development)
|
||||
```bash
|
||||
./gradlew assembleDebug
|
||||
# Output: app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
Features:
|
||||
- WebView debugging enabled
|
||||
- No code obfuscation
|
||||
- Faster build times
|
||||
- Debug logging
|
||||
|
||||
### Release Build (Production)
|
||||
```bash
|
||||
./gradlew assembleRelease
|
||||
# Output: app/build/outputs/apk/release/app-release.apk
|
||||
```
|
||||
|
||||
Features:
|
||||
- Code obfuscation (ProGuard)
|
||||
- Optimized APK size
|
||||
- Production-ready
|
||||
|
||||
## Gradle Tasks
|
||||
|
||||
```bash
|
||||
# Clean build files
|
||||
./gradlew clean
|
||||
|
||||
# Build both debug and release
|
||||
./gradlew assemble
|
||||
|
||||
# Run unit tests
|
||||
./gradlew test
|
||||
|
||||
# Generate test coverage report
|
||||
./gradlew jacocoTestReport
|
||||
|
||||
# Lint check
|
||||
./gradlew lint
|
||||
|
||||
# List all tasks
|
||||
./gradlew tasks
|
||||
```
|
||||
|
||||
## Android Bundle (AAB)
|
||||
|
||||
For Google Play Store submission:
|
||||
|
||||
```bash
|
||||
./gradlew bundleRelease
|
||||
# Output: app/build/outputs/bundle/release/app-release.aab
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gradle Sync Failed
|
||||
1. Check internet connection
|
||||
2. Update Android Studio
|
||||
3. Invalidate caches: File → Invalidate Caches / Restart
|
||||
|
||||
### Build Failed
|
||||
1. Clean project: `./gradlew clean`
|
||||
2. Check JDK version (must be JDK 17)
|
||||
3. Update Android SDK via SDK Manager
|
||||
|
||||
### Device Not Detected
|
||||
1. Enable USB debugging on device
|
||||
2. Install device drivers (Windows)
|
||||
3. Check `adb devices` command
|
||||
4. Try different USB cable/port
|
||||
|
||||
### Out of Memory
|
||||
Increase Gradle memory in `gradle.properties`:
|
||||
```properties
|
||||
org.gradle.jvmargs=-Xmx4096m
|
||||
```
|
||||
|
||||
## Requirements Checklist
|
||||
|
||||
- ✅ Android Studio Ladybug (2024.2.1+)
|
||||
- ✅ JDK 17 or later
|
||||
- ✅ Android SDK 35 (via SDK Manager)
|
||||
- ✅ Build Tools 34.0.0+
|
||||
- ✅ Kotlin 2.1.0+
|
||||
- ✅ Gradle 8.7+
|
||||
|
||||
## First Build
|
||||
|
||||
The first build will take longer as Gradle downloads dependencies:
|
||||
- Jetpack Compose libraries (~150MB)
|
||||
- Material Design 3 components
|
||||
- Room database libraries
|
||||
- Kotlin coroutines
|
||||
- WebView libraries
|
||||
|
||||
Subsequent builds are much faster due to caching.
|
||||
|
||||
## CI/CD with TeamCity
|
||||
|
||||
This project can integrate with your TeamCity instance:
|
||||
|
||||
**TeamCity URL:** https://cis1.silverlabs.uk
|
||||
**Access Token:** (See ~/.claude/CLAUDE.md)
|
||||
|
||||
Build configuration:
|
||||
```kotlin
|
||||
project {
|
||||
buildType {
|
||||
name = "SilverDROID"
|
||||
vcs {
|
||||
root(GitLabVcs)
|
||||
}
|
||||
steps {
|
||||
gradle {
|
||||
tasks = "clean assembleRelease"
|
||||
gradleWrapperPath = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Need help? Check the main README.md or open an issue on GitLab.
|
||||
168
BUILD_INSTRUCTIONS.md
Normal file
168
BUILD_INSTRUCTIONS.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Build Instructions for Dark Side Admin APK
|
||||
|
||||
## ⚠️ Important: This is a Custom Build
|
||||
|
||||
This version of SilverDROID loads `https://admin.dark.side` directly on launch.
|
||||
|
||||
---
|
||||
|
||||
## Option 1: Build with Android Studio (Recommended)
|
||||
|
||||
### Steps:
|
||||
|
||||
1. **Open Project**
|
||||
```
|
||||
C:\Production\Source\SilverLABS\SilverDROID
|
||||
```
|
||||
- Launch Android Studio
|
||||
- File → Open → Select `SilverDROID` folder
|
||||
|
||||
2. **Sync Gradle**
|
||||
- Wait for automatic Gradle sync (~2-5 minutes)
|
||||
- If prompted, click "Sync Now"
|
||||
|
||||
3. **Build APK**
|
||||
- Build → Build Bundle(s) / APK(s) → Build APK(s)
|
||||
- Wait for build to complete
|
||||
- Click "locate" in notification to find APK
|
||||
|
||||
4. **APK Location**
|
||||
```
|
||||
app\build\outputs\apk\debug\app-debug.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Option 2: Build from Command Line (Windows PowerShell)
|
||||
|
||||
### Prerequisites:
|
||||
- Android SDK installed
|
||||
- `ANDROID_HOME` environment variable set
|
||||
|
||||
### Steps:
|
||||
|
||||
1. **Open PowerShell**
|
||||
```powershell
|
||||
cd C:\Production\Source\SilverLABS\SilverDROID
|
||||
```
|
||||
|
||||
2. **Download Gradle Wrapper** (first time only)
|
||||
```powershell
|
||||
# Download Gradle distribution
|
||||
Invoke-WebRequest -Uri "https://services.gradle.org/distributions/gradle-8.9-bin.zip" -OutFile "gradle.zip"
|
||||
|
||||
# Extract
|
||||
Expand-Archive -Path "gradle.zip" -DestinationPath "." -Force
|
||||
|
||||
# Create wrapper
|
||||
.\gradle-8.9\bin\gradle.bat wrapper
|
||||
|
||||
# Cleanup
|
||||
Remove-Item gradle.zip
|
||||
Remove-Item -Recurse -Force gradle-8.9
|
||||
```
|
||||
|
||||
3. **Build Debug APK**
|
||||
```powershell
|
||||
.\gradlew.bat assembleDebug
|
||||
```
|
||||
|
||||
4. **Output Location**
|
||||
```
|
||||
app\build\outputs\apk\debug\app-debug.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Option 3: Use TeamCity CI/CD
|
||||
|
||||
Upload the project to GitLab and configure TeamCity:
|
||||
|
||||
**GitLab:** https://gitlab.silverlabs.uk
|
||||
**TeamCity:** https://cis1.silverlabs.uk
|
||||
|
||||
### TeamCity Build Steps:
|
||||
```kotlin
|
||||
steps {
|
||||
gradle {
|
||||
tasks = "clean assembleDebug"
|
||||
gradleWrapperPath = ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Installing the APK
|
||||
|
||||
### On Physical Device:
|
||||
1. Copy `app-debug.apk` to your device
|
||||
2. Tap the APK file
|
||||
3. Allow "Install from Unknown Sources" if prompted
|
||||
4. Tap "Install"
|
||||
|
||||
### Via ADB:
|
||||
```powershell
|
||||
adb install app\build\outputs\apk\debug\app-debug.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What This Build Does
|
||||
|
||||
- ✅ Loads `https://admin.dark.side` immediately on launch
|
||||
- ✅ Bypasses the launcher screen
|
||||
- ✅ Full WASM/PWA support enabled
|
||||
- ✅ Glassmorphism UI for top bar
|
||||
- ✅ Back button exits the app
|
||||
|
||||
---
|
||||
|
||||
## Customization
|
||||
|
||||
To change the URL, edit:
|
||||
```
|
||||
app/src/main/kotlin/uk/silverlabs/silverdroid/MainActivity.kt
|
||||
```
|
||||
|
||||
Line 23:
|
||||
```kotlin
|
||||
private val targetUrl = "https://admin.dark.side"
|
||||
```
|
||||
|
||||
Change to your desired URL and rebuild.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "SDK location not found"
|
||||
Set `ANDROID_HOME`:
|
||||
```powershell
|
||||
[System.Environment]::SetEnvironmentVariable("ANDROID_HOME", "C:\Users\YourUser\AppData\Local\Android\Sdk", "User")
|
||||
```
|
||||
|
||||
### "Gradle sync failed"
|
||||
1. Check internet connection
|
||||
2. Delete `.gradle` folder
|
||||
3. Restart Android Studio
|
||||
4. Try sync again
|
||||
|
||||
### "Build failed"
|
||||
1. Check JDK version: `java -version` (must be 17+)
|
||||
2. Clean project: `.\gradlew.bat clean`
|
||||
3. Rebuild: `.\gradlew.bat assembleDebug`
|
||||
|
||||
---
|
||||
|
||||
## Build Configuration
|
||||
|
||||
- **Package Name:** `uk.silverlabs.silverdroid`
|
||||
- **App Name:** "Dark Side Admin"
|
||||
- **Min SDK:** 26 (Android 8.0)
|
||||
- **Target SDK:** 35 (Android 15)
|
||||
- **Version:** 1.0.0
|
||||
|
||||
---
|
||||
|
||||
Need help? Check the main README.md or contact SilverLABS support.
|
||||
353
CICD_SUMMARY.md
Normal file
353
CICD_SUMMARY.md
Normal file
@@ -0,0 +1,353 @@
|
||||
# GitLab CI/CD Setup - Complete Summary
|
||||
|
||||
## ✅ What Was Configured
|
||||
|
||||
A fully automated Android CI/CD pipeline for SilverDROID that builds, tests, and deploys your Dark Side Admin APK.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Files Created
|
||||
|
||||
### 1. `.gitlab-ci.yml`
|
||||
**Complete CI/CD pipeline configuration with:**
|
||||
- 4 stages: prepare, test, build, deploy
|
||||
- 12 jobs covering all aspects of Android development
|
||||
- Automatic caching for faster builds
|
||||
- Artifact storage for 30-90 days
|
||||
- Parallel job execution
|
||||
|
||||
### 2. `GITLAB_CICD_SETUP.md`
|
||||
**Comprehensive setup guide including:**
|
||||
- Step-by-step GitLab project creation
|
||||
- Runner configuration instructions
|
||||
- Pipeline architecture explanation
|
||||
- Troubleshooting section
|
||||
- Advanced features guide
|
||||
|
||||
### 3. `push-to-gitlab.sh`
|
||||
**Bash script for automated push:**
|
||||
- Initializes git repository
|
||||
- Configures remote
|
||||
- Commits changes
|
||||
- Pushes to GitLab
|
||||
- Updates project metadata
|
||||
|
||||
### 4. `push-to-gitlab.ps1`
|
||||
**PowerShell version for Windows:**
|
||||
- Same functionality as bash script
|
||||
- Windows-friendly colored output
|
||||
- Error handling for Windows environments
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Option 1: Automated Push (Recommended)
|
||||
|
||||
**From WSL/Linux:**
|
||||
```bash
|
||||
cd /mnt/c/Production/Source/SilverLABS/SilverDROID
|
||||
./push-to-gitlab.sh
|
||||
```
|
||||
|
||||
**From Windows PowerShell:**
|
||||
```powershell
|
||||
cd C:\Production\Source\SilverLABS\SilverDROID
|
||||
.\push-to-gitlab.ps1
|
||||
```
|
||||
|
||||
### Option 2: Manual Push
|
||||
|
||||
```bash
|
||||
cd /mnt/c/Production/Source/SilverLABS/SilverDROID
|
||||
|
||||
# Initialize and configure
|
||||
git init
|
||||
git remote add origin https://gitlab.silverlabs.uk/SilverLABS/silverdroid.git
|
||||
|
||||
# Commit and push
|
||||
git add .
|
||||
git commit -m "Initial commit - SilverDROID CI/CD"
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Pipeline Overview
|
||||
|
||||
### Build Stages
|
||||
|
||||
```
|
||||
prepare (1 min)
|
||||
↓
|
||||
test (2 min)
|
||||
├─ lint
|
||||
├─ unit_tests
|
||||
└─ security:scan
|
||||
↓
|
||||
build (3-5 min)
|
||||
├─ build:debug
|
||||
├─ build:release
|
||||
└─ build:bundle
|
||||
↓
|
||||
deploy (30 sec)
|
||||
├─ deploy:staging
|
||||
├─ deploy:production
|
||||
├─ apk:info
|
||||
└─ notify:*
|
||||
```
|
||||
|
||||
### Total Time: ~5-8 minutes
|
||||
|
||||
---
|
||||
|
||||
## 📦 Build Outputs
|
||||
|
||||
### Debug APK
|
||||
- **Path:** `app/build/outputs/apk/debug/app-debug.apk`
|
||||
- **Size:** ~10-15 MB
|
||||
- **Retention:** 30 days
|
||||
- **Triggers:** All branches
|
||||
|
||||
### Release APK
|
||||
- **Path:** `app/build/outputs/apk/release/app-release-unsigned.apk`
|
||||
- **Size:** ~8-10 MB
|
||||
- **Retention:** 90 days
|
||||
- **Triggers:** main branch, tags
|
||||
|
||||
### Android App Bundle (AAB)
|
||||
- **Path:** `app/build/outputs/bundle/release/app-release.aab`
|
||||
- **Size:** ~8-10 MB
|
||||
- **Retention:** 90 days
|
||||
- **Triggers:** main branch, tags
|
||||
- **Use:** Google Play Store submission
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Features
|
||||
|
||||
### Automatic Features
|
||||
✅ **Gradle Caching** - 90% faster subsequent builds
|
||||
✅ **Parallel Jobs** - Multiple builds run simultaneously
|
||||
✅ **Artifact Storage** - All APKs saved and downloadable
|
||||
✅ **Test Reports** - JUnit XML format
|
||||
✅ **Lint Reports** - HTML and XML format
|
||||
✅ **Security Scanning** - Dependency vulnerability checks
|
||||
✅ **Build Metadata** - Size, commit, date tracked
|
||||
|
||||
### Manual Features
|
||||
⚙️ **Production Deployment** - Manual approval required
|
||||
⚙️ **Signed APKs** - Optional keystore configuration
|
||||
⚙️ **Notifications** - Slack/Mattermost webhooks
|
||||
⚙️ **TeamCity Integration** - Trigger external builds
|
||||
|
||||
---
|
||||
|
||||
## 📊 Pipeline Jobs
|
||||
|
||||
| Job | Purpose | Triggers | Output |
|
||||
|-----|---------|----------|--------|
|
||||
| **prepare:dependencies** | Cache Gradle deps | All | Cached `.gradle/` |
|
||||
| **lint** | Code quality | All | HTML/XML reports |
|
||||
| **unit_tests** | Run tests | All | JUnit XML |
|
||||
| **security:scan** | Vulnerability scan | main/develop | Security report |
|
||||
| **build:debug** | Debug APK | All | app-debug.apk |
|
||||
| **build:release** | Release APK | main/tags | app-release.apk |
|
||||
| **build:bundle** | AAB bundle | main/tags | app-release.aab |
|
||||
| **deploy:staging** | Stage deployment | develop | Staging env |
|
||||
| **deploy:production** | Prod deployment | tags | Production env |
|
||||
| **apk:info** | Build metadata | main/develop | apk-info.txt |
|
||||
| **notify:success** | Success alert | main | Notification |
|
||||
| **notify:failure** | Failure alert | main | Notification |
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Access URLs
|
||||
|
||||
### GitLab Project
|
||||
```
|
||||
https://gitlab.silverlabs.uk/SilverLABS/silverdroid
|
||||
```
|
||||
|
||||
### Pipeline Dashboard
|
||||
```
|
||||
https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/pipelines
|
||||
```
|
||||
|
||||
### Jobs List
|
||||
```
|
||||
https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/jobs
|
||||
```
|
||||
|
||||
### Download Latest Debug APK
|
||||
```
|
||||
https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/jobs/artifacts/main/raw/app/build/outputs/apk/debug/app-debug.apk?job=build:debug
|
||||
```
|
||||
|
||||
### Download Latest Release APK
|
||||
```
|
||||
https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/jobs/artifacts/main/raw/app/build/outputs/apk/release/app-release-unsigned.apk?job=build:release
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security & Signing
|
||||
|
||||
### Optional: APK Signing
|
||||
|
||||
To sign release APKs, add these GitLab CI/CD variables:
|
||||
|
||||
**Settings → CI/CD → Variables:**
|
||||
- `KEYSTORE_FILE` - Base64 encoded keystore
|
||||
- `KEYSTORE_PASSWORD` - Keystore password
|
||||
- `KEY_ALIAS` - Key alias (e.g., "silverdroid")
|
||||
- `KEY_PASSWORD` - Key password
|
||||
|
||||
**Generate keystore:**
|
||||
```bash
|
||||
keytool -genkey -v -keystore silverdroid.keystore \
|
||||
-alias silverdroid -keyalg RSA -keysize 2048 -validity 10000
|
||||
|
||||
base64 -w 0 silverdroid.keystore > keystore.base64
|
||||
```
|
||||
|
||||
Then update `.gitlab-ci.yml` to use signing (see GITLAB_CICD_SETUP.md).
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Workflow
|
||||
|
||||
### Development Workflow
|
||||
|
||||
1. **Make changes** to code
|
||||
2. **Commit** to feature branch
|
||||
3. **Push** to GitLab
|
||||
4. **Pipeline runs** automatically
|
||||
5. **Debug APK** generated
|
||||
6. **Download** from artifacts
|
||||
7. **Test** on device
|
||||
|
||||
### Release Workflow
|
||||
|
||||
1. **Merge** to main branch
|
||||
2. **Tag** release: `git tag v1.0.0`
|
||||
3. **Push tag:** `git push origin v1.0.0`
|
||||
4. **Pipeline runs** with release builds
|
||||
5. **Release APK + AAB** generated
|
||||
6. **Manual approval** for production
|
||||
7. **Download** signed APK/AAB
|
||||
8. **Deploy** to Play Store
|
||||
|
||||
---
|
||||
|
||||
## 📈 Monitoring
|
||||
|
||||
### Pipeline Status Badge
|
||||
|
||||
Add to README.md:
|
||||
```markdown
|
||||
[](https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/pipelines)
|
||||
```
|
||||
|
||||
### Check Pipeline Status via API
|
||||
|
||||
```bash
|
||||
curl "https://gitlab.silverlabs.uk/api/v4/projects/SilverLABS%2Fsilverdroid/pipelines" \
|
||||
--header "PRIVATE-TOKEN: glpat-wqUcD7mg53F1mgM-N-PdiW86MQp1OjEH.01.0w074ox93"
|
||||
```
|
||||
|
||||
### Download Latest APK via API
|
||||
|
||||
```bash
|
||||
curl -L "https://gitlab.silverlabs.uk/api/v4/projects/SilverLABS%2Fsilverdroid/jobs/artifacts/main/download?job=build:debug" \
|
||||
--header "PRIVATE-TOKEN: glpat-wqUcD7mg53F1mgM-N-PdiW86MQp1OjEH.01.0w074ox93" \
|
||||
-o silverdroid-debug.zip
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Pipeline Doesn't Start
|
||||
- Check GitLab Runner is active: Settings → CI/CD → Runners
|
||||
- Verify `.gitlab-ci.yml` is in root directory
|
||||
- Check syntax: CI/CD → Pipelines → CI Lint
|
||||
|
||||
### Build Fails: "Gradle not found"
|
||||
- Runner may not have Android SDK
|
||||
- Using `mingc/android-build-box` image fixes this
|
||||
- Check `image:` in `.gitlab-ci.yml`
|
||||
|
||||
### Can't Download Artifacts
|
||||
- Check artifact expiration hasn't passed
|
||||
- Verify job completed successfully (green checkmark)
|
||||
- Try direct download URL (see above)
|
||||
|
||||
### Runner Out of Disk Space
|
||||
```bash
|
||||
# On GitLab server
|
||||
gitlab-runner exec docker cleanup
|
||||
docker system prune -a
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
1. **Push to GitLab**
|
||||
```bash
|
||||
./push-to-gitlab.sh # or .ps1 on Windows
|
||||
```
|
||||
|
||||
2. **Watch Pipeline Run**
|
||||
Visit: https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/pipelines
|
||||
|
||||
3. **Wait for Build** (~5-8 minutes)
|
||||
|
||||
4. **Download APK**
|
||||
Go to: Jobs → build:debug → Browse → app/build/outputs/apk/debug/
|
||||
|
||||
5. **Install on Device**
|
||||
```bash
|
||||
adb install app-debug.apk
|
||||
```
|
||||
|
||||
6. **Test Admin Panel**
|
||||
Launch app → Should load admin.dark.side
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- **GITLAB_CICD_SETUP.md** - Complete setup guide
|
||||
- **BUILD.md** - Manual build instructions
|
||||
- **DARK_SIDE_BUILD.md** - Custom build info
|
||||
- **.gitlab-ci.yml** - Pipeline configuration
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
- [x] `.gitlab-ci.yml` created
|
||||
- [x] Push scripts created (bash + PowerShell)
|
||||
- [x] Documentation written
|
||||
- [ ] **Push to GitLab** ← Next Step
|
||||
- [ ] Verify pipeline runs
|
||||
- [ ] Download APK
|
||||
- [ ] Test on device
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Ready to Go!
|
||||
|
||||
Your CI/CD pipeline is fully configured. Run the push script to get started:
|
||||
|
||||
```bash
|
||||
./push-to-gitlab.sh
|
||||
```
|
||||
|
||||
Or manually push and watch the magic happen! ✨
|
||||
|
||||
---
|
||||
|
||||
**Built for SilverLABS** | Automated Android Builds
|
||||
307
DARK_SIDE_BUILD.md
Normal file
307
DARK_SIDE_BUILD.md
Normal file
@@ -0,0 +1,307 @@
|
||||
# Dark Side Admin - Custom Build Summary
|
||||
|
||||
## 🎯 What Was Created
|
||||
|
||||
A specialized version of SilverDROID that loads **`https://admin.dark.side`** directly on launch, with full WASM/PWA support and glassmorphism UI.
|
||||
|
||||
---
|
||||
|
||||
## 📦 Project Status
|
||||
|
||||
### ✅ Completed
|
||||
- Android project structure created
|
||||
- MainActivity modified to auto-load admin.dark.side
|
||||
- WebView optimized for WASM/PWA
|
||||
- Glassmorphism UI theme applied
|
||||
- App name changed to "Dark Side Admin"
|
||||
- All dependencies configured
|
||||
|
||||
### 📱 Ready for Compilation
|
||||
The project is **ready to build** but requires:
|
||||
1. **Android Studio** (to compile)
|
||||
2. **Android SDK** installed
|
||||
3. **JDK 17+** installed
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ How to Build
|
||||
|
||||
### Recommended: Android Studio
|
||||
|
||||
1. **Open Android Studio**
|
||||
- File → Open
|
||||
- Navigate to: `C:\Production\Source\SilverLABS\SilverDROID`
|
||||
|
||||
2. **Sync Gradle** (automatic, ~2-5 minutes)
|
||||
|
||||
3. **Build APK**
|
||||
- Build → Build Bundle(s) / APK(s) → Build APK(s)
|
||||
|
||||
4. **Find APK**
|
||||
```
|
||||
app\build\outputs\apk\debug\app-debug.apk
|
||||
```
|
||||
|
||||
### Alternative: PowerShell Command Line
|
||||
|
||||
```powershell
|
||||
cd C:\Production\Source\SilverLABS\SilverDROID
|
||||
|
||||
# First time: Create Gradle wrapper
|
||||
.\gradlew.bat wrapper
|
||||
|
||||
# Build APK
|
||||
.\gradlew.bat assembleDebug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 What's Included
|
||||
|
||||
### Core Features
|
||||
- ✅ **Direct URL Loading** - Opens admin.dark.side immediately
|
||||
- ✅ **WASM Support** - WebAssembly fully enabled
|
||||
- ✅ **PWA Features** - Service Workers, offline caching
|
||||
- ✅ **DOM Storage** - LocalStorage, SessionStorage enabled
|
||||
- ✅ **JavaScript** - Fully enabled
|
||||
- ✅ **Mixed Content** - HTTP/HTTPS allowed (for dev)
|
||||
|
||||
### UI Features
|
||||
- ✅ **Glassmorphism Theme** - Frosted glass effects
|
||||
- ✅ **Material Design 3** - Modern Android design
|
||||
- ✅ **Dark/Light Theme** - Adapts to system settings
|
||||
- ✅ **Progress Indicator** - Loading states
|
||||
- ✅ **Back Navigation** - Back button exits app
|
||||
- ✅ **Refresh Button** - Reload the page
|
||||
- ✅ **Title Bar** - Shows page title
|
||||
|
||||
### WebView Configuration
|
||||
```kotlin
|
||||
settings.javaScriptEnabled = true
|
||||
settings.domStorageEnabled = true
|
||||
settings.databaseEnabled = true
|
||||
settings.allowFileAccess = true
|
||||
settings.allowContentAccess = true
|
||||
settings.mixedContentMode = MIXED_CONTENT_ALWAYS_ALLOW
|
||||
settings.cacheMode = LOAD_DEFAULT
|
||||
settings.setAppCacheEnabled(true)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 Key Files Modified
|
||||
|
||||
### MainActivity.kt
|
||||
**Changed:** Now loads admin.dark.side directly instead of showing launcher
|
||||
|
||||
```kotlin
|
||||
private val targetUrl = "https://admin.dark.side"
|
||||
private val appName = "Dark Side Admin"
|
||||
```
|
||||
|
||||
### strings.xml
|
||||
**Changed:** App name from "SilverDROID" to "Dark Side Admin"
|
||||
|
||||
```xml
|
||||
<string name="app_name">Dark Side Admin</string>
|
||||
```
|
||||
|
||||
### All Other Files
|
||||
**Unchanged:** Theme, WebView, components all intact
|
||||
|
||||
---
|
||||
|
||||
## 🔧 WebView Debugging
|
||||
|
||||
### Enable Chrome DevTools
|
||||
|
||||
1. Build and install the app
|
||||
2. Open Chrome on your PC
|
||||
3. Navigate to: `chrome://inspect`
|
||||
4. Find "Dark Side Admin" under "Remote Target"
|
||||
5. Click "inspect"
|
||||
|
||||
Now you have full DevTools:
|
||||
- Console
|
||||
- Network tab
|
||||
- Elements inspector
|
||||
- Performance profiler
|
||||
|
||||
---
|
||||
|
||||
## 📱 App Specifications
|
||||
|
||||
```yaml
|
||||
Package Name: uk.silverlabs.silverdroid
|
||||
App Name: Dark Side Admin
|
||||
Version: 1.0.0
|
||||
Min SDK: 26 (Android 8.0)
|
||||
Target SDK: 35 (Android 15)
|
||||
Compile SDK: 35
|
||||
|
||||
Target URL: https://admin.dark.side
|
||||
|
||||
Permissions:
|
||||
- INTERNET
|
||||
- ACCESS_NETWORK_STATE
|
||||
- POST_NOTIFICATIONS
|
||||
- Storage (for caching)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
### On Device
|
||||
1. Build the APK (see above)
|
||||
2. Copy to device via USB or cloud
|
||||
3. Tap the APK file
|
||||
4. Allow "Install from Unknown Sources"
|
||||
5. Tap "Install"
|
||||
|
||||
### Via ADB
|
||||
```bash
|
||||
adb install app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Security Notes
|
||||
|
||||
### Cleartext Traffic
|
||||
Currently enabled for development. For production:
|
||||
|
||||
Edit `AndroidManifest.xml`:
|
||||
```xml
|
||||
android:usesCleartextTraffic="false"
|
||||
```
|
||||
|
||||
### Mixed Content
|
||||
Currently allows HTTP/HTTPS mixing. For production, change WebView settings:
|
||||
```kotlin
|
||||
settings.mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
|
||||
```
|
||||
|
||||
### ProGuard
|
||||
Release builds use ProGuard for code obfuscation and optimization.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Build Outputs
|
||||
|
||||
### Debug Build
|
||||
- **File:** `app-debug.apk`
|
||||
- **Size:** ~10-15 MB
|
||||
- **Debuggable:** Yes
|
||||
- **Optimized:** No
|
||||
- **Location:** `app/build/outputs/apk/debug/`
|
||||
|
||||
### Release Build
|
||||
```powershell
|
||||
.\gradlew.bat assembleRelease
|
||||
```
|
||||
- **File:** `app-release-unsigned.apk`
|
||||
- **Size:** ~8-10 MB
|
||||
- **Debuggable:** No
|
||||
- **Optimized:** Yes (ProGuard)
|
||||
- **Location:** `app/build/outputs/apk/release/`
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Customization
|
||||
|
||||
### Change Target URL
|
||||
Edit `MainActivity.kt` line 23:
|
||||
```kotlin
|
||||
private val targetUrl = "https://your-new-url.com"
|
||||
```
|
||||
|
||||
### Change App Name
|
||||
Edit `res/values/strings.xml`:
|
||||
```xml
|
||||
<string name="app_name">Your App Name</string>
|
||||
```
|
||||
|
||||
### Change Package Name
|
||||
1. Refactor package in Android Studio
|
||||
2. Update `build.gradle.kts`:
|
||||
```kotlin
|
||||
applicationId = "com.yourcompany.appname"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Build Errors
|
||||
|
||||
**"SDK location not found"**
|
||||
```powershell
|
||||
# Set ANDROID_HOME environment variable
|
||||
$env:ANDROID_HOME = "C:\Users\YourUser\AppData\Local\Android\Sdk"
|
||||
```
|
||||
|
||||
**"Gradle sync failed"**
|
||||
1. Check internet connection
|
||||
2. File → Invalidate Caches / Restart
|
||||
|
||||
**"JDK version incompatible"**
|
||||
- Need JDK 17 or later
|
||||
- Download from: https://adoptium.net/
|
||||
|
||||
### Runtime Errors
|
||||
|
||||
**"App won't load URL"**
|
||||
- Check internet connection
|
||||
- Verify URL is accessible in browser
|
||||
- Check logcat: `adb logcat | grep SilverDROID`
|
||||
|
||||
**"WebView blank"**
|
||||
- Clear app data: Settings → Apps → Dark Side Admin → Clear Data
|
||||
- Try different URL to test
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
### Resources
|
||||
- **Main README:** `README.md`
|
||||
- **Build Guide:** `BUILD.md`
|
||||
- **Quick Start:** `QUICKSTART.md`
|
||||
- **Project Summary:** `PROJECT_SUMMARY.md`
|
||||
|
||||
### SilverLABS Infrastructure
|
||||
- **GitLab:** https://gitlab.silverlabs.uk
|
||||
- **TeamCity:** https://cis1.silverlabs.uk
|
||||
- **Knowledge Base:** `~/.claude/Knowledge/`
|
||||
|
||||
---
|
||||
|
||||
## ✅ Build Checklist
|
||||
|
||||
- [x] Project structure created
|
||||
- [x] MainActivity modified for direct load
|
||||
- [x] App name changed
|
||||
- [x] WebView configured for WASM
|
||||
- [x] Glassmorphism theme applied
|
||||
- [x] Gradle wrapper created
|
||||
- [x] Build scripts ready
|
||||
- [ ] **Compile in Android Studio** ← Next Step
|
||||
- [ ] Install on device
|
||||
- [ ] Test admin.dark.side loading
|
||||
|
||||
---
|
||||
|
||||
## 🎉 Summary
|
||||
|
||||
You now have a **fully configured Android project** that:
|
||||
- Loads `https://admin.dark.side` on launch
|
||||
- Supports WASM and PWAs
|
||||
- Has beautiful glassmorphism UI
|
||||
- Is ready to compile
|
||||
|
||||
**Next step:** Open in Android Studio and build the APK!
|
||||
|
||||
---
|
||||
|
||||
**Built for SilverLABS** | Custom Dark Side Admin Loader
|
||||
471
GITLAB_CICD_SETUP.md
Normal file
471
GITLAB_CICD_SETUP.md
Normal file
@@ -0,0 +1,471 @@
|
||||
# GitLab CI/CD Setup for SilverDROID
|
||||
|
||||
Complete guide for setting up automated Android builds on your GitLab CE instance.
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Overview
|
||||
|
||||
This CI/CD pipeline automatically:
|
||||
- ✅ Builds Debug and Release APKs
|
||||
- ✅ Runs lint checks and unit tests
|
||||
- ✅ Generates Android App Bundles (AAB)
|
||||
- ✅ Stores build artifacts
|
||||
- ✅ Creates deployment environments
|
||||
- ✅ Sends build notifications
|
||||
|
||||
---
|
||||
|
||||
## 📋 Prerequisites
|
||||
|
||||
### 1. GitLab CE Instance
|
||||
**URL:** https://gitlab.silverlabs.uk
|
||||
**Access Token:** `glpat-wqUcD7mg53F1mgM-N-PdiW86MQp1OjEH.01.0w074ox93`
|
||||
|
||||
### 2. GitLab Runner
|
||||
You need at least one GitLab Runner configured with:
|
||||
- Docker executor
|
||||
- Sufficient resources (4GB RAM, 20GB disk)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Setup
|
||||
|
||||
### Step 1: Initialize Git Repository
|
||||
|
||||
```bash
|
||||
cd /mnt/c/Production/Source/SilverLABS/SilverDROID
|
||||
|
||||
# Initialize git (if not already)
|
||||
git init
|
||||
|
||||
# Add remote
|
||||
git remote add origin https://gitlab.silverlabs.uk/SilverLABS/silverdroid.git
|
||||
|
||||
# Add all files
|
||||
git add .
|
||||
|
||||
# Commit
|
||||
git commit -m "Initial commit - SilverDROID Dark Side Admin"
|
||||
|
||||
# Push to GitLab
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
### Step 2: Create GitLab Project
|
||||
|
||||
**Via GitLab Web UI:**
|
||||
1. Go to https://gitlab.silverlabs.uk
|
||||
2. Click "New Project"
|
||||
3. Choose "Create blank project"
|
||||
4. Project name: `silverdroid`
|
||||
5. Namespace: `SilverLABS`
|
||||
6. Visibility: `Private`
|
||||
7. Click "Create project"
|
||||
|
||||
**Or via API:**
|
||||
```bash
|
||||
curl --request POST "https://gitlab.silverlabs.uk/api/v4/projects" \
|
||||
--header "PRIVATE-TOKEN: glpat-wqUcD7mg53F1mgM-N-PdiW86MQp1OjEH.01.0w074ox93" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data '{
|
||||
"name": "SilverDROID",
|
||||
"path": "silverdroid",
|
||||
"namespace_id": <SilverLABS_group_id>,
|
||||
"visibility": "private",
|
||||
"description": "Android PWA/WASM Launcher - Dark Side Admin"
|
||||
}'
|
||||
```
|
||||
|
||||
### Step 3: Configure GitLab Runner
|
||||
|
||||
**Check existing runners:**
|
||||
```bash
|
||||
# On your GitLab server
|
||||
gitlab-runner list
|
||||
```
|
||||
|
||||
**Register a new runner (if needed):**
|
||||
```bash
|
||||
gitlab-runner register \
|
||||
--url https://gitlab.silverlabs.uk \
|
||||
--registration-token <PROJECT_REGISTRATION_TOKEN> \
|
||||
--executor docker \
|
||||
--docker-image mingc/android-build-box:latest \
|
||||
--description "Android Build Runner" \
|
||||
--tag-list "android,docker" \
|
||||
--docker-privileged=false \
|
||||
--docker-volumes "/cache"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Pipeline Architecture
|
||||
|
||||
### Stages
|
||||
|
||||
```yaml
|
||||
stages:
|
||||
- prepare # Download dependencies
|
||||
- test # Lint, unit tests, security scan
|
||||
- build # Build APKs and AAB
|
||||
- deploy # Deploy artifacts, notifications
|
||||
```
|
||||
|
||||
### Jobs Overview
|
||||
|
||||
| Job | Stage | Trigger | Output |
|
||||
|-----|-------|---------|--------|
|
||||
| `prepare:dependencies` | prepare | All branches | Cached Gradle deps |
|
||||
| `lint` | test | All branches | Lint reports |
|
||||
| `unit_tests` | test | All branches | Test results |
|
||||
| `security:scan` | test | main/develop | Security report |
|
||||
| `build:debug` | build | All branches | app-debug.apk |
|
||||
| `build:release` | build | main/tags | app-release.apk |
|
||||
| `build:bundle` | build | main/tags | app-release.aab |
|
||||
| `deploy:staging` | deploy | develop | Staging deployment |
|
||||
| `deploy:production` | deploy | tags | Production (manual) |
|
||||
| `apk:info` | deploy | main/develop | Build metadata |
|
||||
| `notify:*` | deploy | main | Notifications |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Pipeline Variables
|
||||
|
||||
Set these in GitLab: **Settings → CI/CD → Variables**
|
||||
|
||||
#### Required Variables
|
||||
None! Pipeline works out of the box.
|
||||
|
||||
#### Optional Variables (for signed APKs)
|
||||
|
||||
```yaml
|
||||
KEYSTORE_FILE # Base64 encoded keystore
|
||||
KEYSTORE_PASSWORD # Keystore password
|
||||
KEY_ALIAS # Key alias
|
||||
KEY_PASSWORD # Key password
|
||||
```
|
||||
|
||||
### Branches Strategy
|
||||
|
||||
- **`main`** - Production builds (Release APK)
|
||||
- **`develop`** - Staging builds (Debug APK)
|
||||
- **`feature/*`** - Feature branches (Debug APK only)
|
||||
- **`tags`** - Release tags (Signed Release + AAB)
|
||||
|
||||
---
|
||||
|
||||
## 📦 Build Artifacts
|
||||
|
||||
### Artifact Storage
|
||||
|
||||
All artifacts are stored in GitLab and accessible via:
|
||||
|
||||
```
|
||||
Project → CI/CD → Pipelines → [Pipeline] → Jobs → [Job] → Browse
|
||||
```
|
||||
|
||||
### Download URLs
|
||||
|
||||
**Debug APK:**
|
||||
```
|
||||
https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/jobs/artifacts/main/raw/app/build/outputs/apk/debug/app-debug.apk?job=build:debug
|
||||
```
|
||||
|
||||
**Release APK:**
|
||||
```
|
||||
https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/jobs/artifacts/main/raw/app/build/outputs/apk/release/app-release-unsigned.apk?job=build:release
|
||||
```
|
||||
|
||||
### Retention
|
||||
|
||||
- Debug APKs: 30 days
|
||||
- Release APKs: 90 days
|
||||
- Test reports: 7 days
|
||||
- AAB bundles: 90 days
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Pipeline Badges
|
||||
|
||||
Add to your README.md:
|
||||
|
||||
```markdown
|
||||
[](https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/commits/main)
|
||||
|
||||
[](https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/commits/main)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Signing Release APKs
|
||||
|
||||
### Step 1: Create Keystore
|
||||
|
||||
```bash
|
||||
keytool -genkey -v -keystore silverdroid.keystore \
|
||||
-alias silverdroid -keyalg RSA -keysize 2048 -validity 10000
|
||||
```
|
||||
|
||||
### Step 2: Encode Keystore
|
||||
|
||||
```bash
|
||||
base64 -w 0 silverdroid.keystore > keystore.base64
|
||||
```
|
||||
|
||||
### Step 3: Add to GitLab Variables
|
||||
|
||||
1. Go to **Settings → CI/CD → Variables**
|
||||
2. Add these variables (all marked as "Protected" and "Masked"):
|
||||
|
||||
```
|
||||
KEYSTORE_FILE = <contents of keystore.base64>
|
||||
KEYSTORE_PASSWORD = <your keystore password>
|
||||
KEY_ALIAS = silverdroid
|
||||
KEY_PASSWORD = <your key password>
|
||||
```
|
||||
|
||||
### Step 4: Update .gitlab-ci.yml
|
||||
|
||||
Add signing configuration to `build:release`:
|
||||
|
||||
```yaml
|
||||
build:release:
|
||||
stage: build
|
||||
before_script:
|
||||
- echo $KEYSTORE_FILE | base64 -d > silverdroid.keystore
|
||||
- export KEYSTORE_FILE=silverdroid.keystore
|
||||
script:
|
||||
- ./gradlew assembleRelease \
|
||||
-Pandroid.injected.signing.store.file=$KEYSTORE_FILE \
|
||||
-Pandroid.injected.signing.store.password=$KEYSTORE_PASSWORD \
|
||||
-Pandroid.injected.signing.key.alias=$KEY_ALIAS \
|
||||
-Pandroid.injected.signing.key.password=$KEY_PASSWORD
|
||||
after_script:
|
||||
- rm -f silverdroid.keystore
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 TeamCity Integration
|
||||
|
||||
Trigger TeamCity builds from GitLab:
|
||||
|
||||
### Add to .gitlab-ci.yml:
|
||||
|
||||
```yaml
|
||||
trigger:teamcity:
|
||||
stage: deploy
|
||||
script:
|
||||
- |
|
||||
curl -X POST "https://cis1.silverlabs.uk/app/rest/buildQueue" \
|
||||
-H "Authorization: Bearer eyJ0eXAiOiAiVENWMiJ9.eWxqS3hnMTNlS0Ezb0hMX0tuSHhkUDJ2eFUw.Y2M1NzRiMzQtNTE2Yy00MjMyLWE5MmEtZTg5OGVjYWNiMjc1" \
|
||||
-H "Content-Type: application/xml" \
|
||||
-d "<build><buildType id='SilverDROID_Build'/></build>"
|
||||
only:
|
||||
- main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Pipeline Status
|
||||
|
||||
View pipeline status:
|
||||
```
|
||||
https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/pipelines
|
||||
```
|
||||
|
||||
### Job Logs
|
||||
|
||||
Access detailed logs:
|
||||
```
|
||||
Project → CI/CD → Jobs → [Select Job]
|
||||
```
|
||||
|
||||
### Build Duration
|
||||
|
||||
Average pipeline duration: ~5-8 minutes
|
||||
- prepare: ~1 min
|
||||
- test: ~2 min
|
||||
- build: ~3-5 min
|
||||
- deploy: ~30 sec
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Pipeline Fails on First Run
|
||||
|
||||
**Issue:** Gradle dependencies download timeout
|
||||
|
||||
**Solution:**
|
||||
1. Increase job timeout: Settings → CI/CD → General pipelines → Timeout
|
||||
2. Set to 30 minutes for first run
|
||||
3. Subsequent runs use cache (~5 min)
|
||||
|
||||
### Runner Out of Memory
|
||||
|
||||
**Issue:** Build fails with "Out of memory" error
|
||||
|
||||
**Solution:**
|
||||
Edit runner config (`/etc/gitlab-runner/config.toml`):
|
||||
```toml
|
||||
[[runners]]
|
||||
[runners.docker]
|
||||
memory = "4g"
|
||||
memory_swap = "4g"
|
||||
```
|
||||
|
||||
Restart runner:
|
||||
```bash
|
||||
gitlab-runner restart
|
||||
```
|
||||
|
||||
### Gradle Wrapper Not Executable
|
||||
|
||||
**Issue:** `Permission denied: ./gradlew`
|
||||
|
||||
**Solution:**
|
||||
Already fixed in pipeline with:
|
||||
```yaml
|
||||
before_script:
|
||||
- chmod +x ./gradlew
|
||||
```
|
||||
|
||||
### Artifacts Not Appearing
|
||||
|
||||
**Issue:** Can't find APK after build
|
||||
|
||||
**Solution:**
|
||||
1. Check job artifacts tab
|
||||
2. Verify artifact expiration hasn't passed
|
||||
3. Check `paths:` in `.gitlab-ci.yml`
|
||||
|
||||
---
|
||||
|
||||
## 🔔 Notifications
|
||||
|
||||
### Slack/Mattermost Integration
|
||||
|
||||
Update notification jobs in `.gitlab-ci.yml`:
|
||||
|
||||
```yaml
|
||||
notify:success:
|
||||
script:
|
||||
- |
|
||||
curl -X POST "YOUR_WEBHOOK_URL" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"text\": \"✅ Build #${CI_PIPELINE_ID} succeeded\",
|
||||
\"username\": \"GitLab CI\",
|
||||
\"icon_emoji\": \":white_check_mark:\"
|
||||
}"
|
||||
```
|
||||
|
||||
### Email Notifications
|
||||
|
||||
Configure in GitLab:
|
||||
1. Settings → Integrations → Pipelines emails
|
||||
2. Add recipient emails
|
||||
3. Check events to notify
|
||||
|
||||
---
|
||||
|
||||
## 📈 Advanced Features
|
||||
|
||||
### Parallel Builds
|
||||
|
||||
Build multiple variants simultaneously:
|
||||
|
||||
```yaml
|
||||
build:variants:
|
||||
stage: build
|
||||
parallel:
|
||||
matrix:
|
||||
- VARIANT: [debug, release]
|
||||
script:
|
||||
- ./gradlew assemble${VARIANT}
|
||||
```
|
||||
|
||||
### Merge Request Pipelines
|
||||
|
||||
Automatic builds on MRs (already configured):
|
||||
```yaml
|
||||
only:
|
||||
- merge_requests
|
||||
```
|
||||
|
||||
### Scheduled Pipelines
|
||||
|
||||
Nightly builds:
|
||||
1. CI/CD → Schedules → New schedule
|
||||
2. Cron: `0 2 * * *` (2 AM daily)
|
||||
3. Target branch: `develop`
|
||||
4. Variables: `BUILD_TYPE=nightly`
|
||||
|
||||
---
|
||||
|
||||
## 📝 Testing the Pipeline
|
||||
|
||||
### Trigger a Build
|
||||
|
||||
```bash
|
||||
# Make a change
|
||||
echo "# Test" >> README.md
|
||||
|
||||
# Commit and push
|
||||
git add README.md
|
||||
git commit -m "Test CI/CD pipeline"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### Monitor Progress
|
||||
|
||||
```bash
|
||||
# View pipeline status
|
||||
curl "https://gitlab.silverlabs.uk/api/v4/projects/SilverLABS%2Fsilverdroid/pipelines?per_page=1" \
|
||||
--header "PRIVATE-TOKEN: glpat-wqUcD7mg53F1mgM-N-PdiW86MQp1OjEH.01.0w074ox93"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist
|
||||
|
||||
- [ ] GitLab project created
|
||||
- [ ] Repository pushed to GitLab
|
||||
- [ ] Runner registered and active
|
||||
- [ ] Pipeline executed successfully
|
||||
- [ ] Artifacts generated and downloadable
|
||||
- [ ] (Optional) Signing configured
|
||||
- [ ] (Optional) Notifications configured
|
||||
- [ ] (Optional) TeamCity integration added
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
### Resources
|
||||
- **GitLab Docs:** https://docs.gitlab.com/ee/ci/
|
||||
- **Docker Image:** https://github.com/mingchen/docker-android-build-box
|
||||
- **Pipeline File:** `.gitlab-ci.yml` (in project root)
|
||||
|
||||
### SilverLABS Infrastructure
|
||||
- **GitLab:** https://gitlab.silverlabs.uk
|
||||
- **TeamCity:** https://cis1.silverlabs.uk
|
||||
- **Token:** (See ~/.claude/CLAUDE.md)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 You're All Set!
|
||||
|
||||
Your Android CI/CD pipeline is ready to:
|
||||
- Automatically build APKs on every push
|
||||
- Run tests and quality checks
|
||||
- Store artifacts for download
|
||||
- Deploy to staging/production
|
||||
|
||||
Push your code and watch the magic happen! ✨
|
||||
326
PROJECT_SUMMARY.md
Normal file
326
PROJECT_SUMMARY.md
Normal file
@@ -0,0 +1,326 @@
|
||||
# SilverDROID - Project Summary
|
||||
|
||||
## 🎯 Project Overview
|
||||
|
||||
**SilverDROID** is an Android launcher application for Progressive Web Apps (PWAs) and WebAssembly (WASM) applications, featuring a beautiful glassmorphism UI inspired by the Electron + Svelte architecture from SilverPOWERSHELL.
|
||||
|
||||
### Core Concept
|
||||
- **PWA Container**: Native Android wrapper for web apps
|
||||
- **WASM Support**: Optimized WebView for WebAssembly execution
|
||||
- **Glassmorphism UI**: Frosted glass aesthetic with Material Design 3
|
||||
- **Launcher Paradigm**: Grid-based app launcher similar to traditional launchers
|
||||
|
||||
## ✅ Implementation Status
|
||||
|
||||
### Completed Features
|
||||
|
||||
#### 1. Project Infrastructure ✅
|
||||
- Gradle build system configured
|
||||
- Android Studio project structure
|
||||
- Dependencies: Jetpack Compose, Room, WebView, Coil
|
||||
- Build configurations (debug/release)
|
||||
- ProGuard rules
|
||||
|
||||
#### 2. UI Layer ✅
|
||||
|
||||
**Theme System**
|
||||
- Material Design 3 color scheme (light/dark)
|
||||
- Custom glassmorphism color palette
|
||||
- Typography system
|
||||
- Dynamic theming support
|
||||
|
||||
**Glass Components** (`ui/components/GlassComponents.kt`)
|
||||
- `GlassCard` - Frosted glass cards with blur
|
||||
- `FrostedGlassPanel` - Elevated glass panels
|
||||
- `GlassBackground` - Animated gradient backgrounds
|
||||
- `ShimmerGlass` - Shimmer effect surfaces
|
||||
- `GlassButton` - Floating glass buttons
|
||||
|
||||
**Launcher Screen** (`ui/launcher/LauncherScreen.kt`)
|
||||
- Grid-based app layout
|
||||
- Empty state with add prompt
|
||||
- Glass-styled top bar
|
||||
- Floating action button
|
||||
- App cards with icons
|
||||
|
||||
#### 3. Data Layer ✅
|
||||
|
||||
**Room Database**
|
||||
- `PwaApp` entity with full schema
|
||||
- `PwaAppDao` with CRUD operations
|
||||
- `PwaDatabase` singleton
|
||||
- Migration support
|
||||
|
||||
**Repository Pattern**
|
||||
- `PwaRepository` for data access
|
||||
- URL-based installation
|
||||
- Last accessed tracking
|
||||
- App count queries
|
||||
|
||||
#### 4. WebView Container ✅
|
||||
|
||||
**WebViewActivity** (`ui/webview/WebViewActivity.kt`)
|
||||
- Full-screen PWA container
|
||||
- Intent-based navigation
|
||||
- Edge-to-edge display
|
||||
|
||||
**WasmWebView** (`ui/webview/WasmWebView.kt`)
|
||||
- JavaScript enabled
|
||||
- WASM support configured
|
||||
- DOM storage for PWAs
|
||||
- Offline caching
|
||||
- Service Worker support
|
||||
- Progress indicators
|
||||
- Back navigation
|
||||
- Console logging
|
||||
- Custom user agent
|
||||
|
||||
#### 5. Business Logic ✅
|
||||
|
||||
**LauncherViewModel**
|
||||
- StateFlow for reactive updates
|
||||
- App list management
|
||||
- Add/delete operations
|
||||
- Last accessed tracking
|
||||
- Sample app generation
|
||||
|
||||
**MainActivity**
|
||||
- Compose integration
|
||||
- Navigation to WebView
|
||||
- Add app dialog
|
||||
- Settings placeholder
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
SilverDROID/
|
||||
├── app/
|
||||
│ ├── build.gradle.kts # App-level Gradle config
|
||||
│ ├── proguard-rules.pro # ProGuard rules
|
||||
│ └── src/main/
|
||||
│ ├── AndroidManifest.xml # App manifest
|
||||
│ ├── kotlin/uk/silverlabs/silverdroid/
|
||||
│ │ ├── MainActivity.kt # Entry point
|
||||
│ │ ├── ui/
|
||||
│ │ │ ├── launcher/
|
||||
│ │ │ │ ├── LauncherScreen.kt # Main screen UI
|
||||
│ │ │ │ └── LauncherViewModel.kt # Screen logic
|
||||
│ │ │ ├── webview/
|
||||
│ │ │ │ ├── WebViewActivity.kt # PWA container
|
||||
│ │ │ │ └── WasmWebView.kt # WebView component
|
||||
│ │ │ ├── components/
|
||||
│ │ │ │ └── GlassComponents.kt # Reusable glass UI
|
||||
│ │ │ └── theme/
|
||||
│ │ │ ├── Color.kt # Color palette
|
||||
│ │ │ ├── Theme.kt # Theme config
|
||||
│ │ │ └── Type.kt # Typography
|
||||
│ │ ├── data/
|
||||
│ │ │ ├── model/
|
||||
│ │ │ │ └── PwaApp.kt # App entity
|
||||
│ │ │ ├── repository/
|
||||
│ │ │ │ └── PwaRepository.kt # Data access
|
||||
│ │ │ ├── PwaDatabase.kt # Room database
|
||||
│ │ │ └── PwaAppDao.kt # Database queries
|
||||
│ │ └── webview/ # (Reserved)
|
||||
│ └── res/
|
||||
│ ├── values/
|
||||
│ │ ├── strings.xml # String resources
|
||||
│ │ ├── colors.xml # XML colors
|
||||
│ │ └── themes.xml # XML themes
|
||||
│ └── xml/
|
||||
│ ├── backup_rules.xml # Backup config
|
||||
│ └── data_extraction_rules.xml # Data rules
|
||||
├── gradle/
|
||||
│ └── wrapper/
|
||||
│ └── gradle-wrapper.properties # Gradle wrapper config
|
||||
├── build.gradle.kts # Root Gradle config
|
||||
├── settings.gradle.kts # Project settings
|
||||
├── gradle.properties # Gradle properties
|
||||
├── .gitignore # Git ignore rules
|
||||
├── README.md # User documentation
|
||||
├── BUILD.md # Build instructions
|
||||
└── PROJECT_SUMMARY.md # This file
|
||||
```
|
||||
|
||||
## 🎨 Design Philosophy
|
||||
|
||||
### Glassmorphism
|
||||
Inspired by modern UI trends and SilverPOWERSHELL:
|
||||
- Semi-transparent surfaces
|
||||
- Soft background blurs
|
||||
- Layered depth (frosted glass effect)
|
||||
- Subtle borders and gradients
|
||||
- Light/dark theme support
|
||||
|
||||
### Material Design 3
|
||||
- Dynamic color system
|
||||
- Adaptive theming
|
||||
- Modern component library
|
||||
- Accessibility compliant
|
||||
|
||||
## 🚀 Technology Choices
|
||||
|
||||
### Why Jetpack Compose?
|
||||
- Modern declarative UI (like Svelte in SilverPOWERSHELL)
|
||||
- Type-safe builders
|
||||
- Built-in animation support
|
||||
- Material Design 3 native support
|
||||
- Better performance than XML layouts
|
||||
|
||||
### Why Room?
|
||||
- Type-safe database queries
|
||||
- Compile-time verification
|
||||
- Flow-based reactive updates
|
||||
- Clean API
|
||||
|
||||
### Why WebView?
|
||||
- Native PWA support
|
||||
- Full WASM compatibility
|
||||
- Service Worker support
|
||||
- Offline capabilities
|
||||
- Chrome engine (Chromium-based)
|
||||
|
||||
## 📊 Performance Considerations
|
||||
|
||||
### Optimizations Implemented
|
||||
1. **WebView**: Hardware acceleration enabled
|
||||
2. **Database**: Flow-based reactive queries (no polling)
|
||||
3. **UI**: Compose lazy grid (virtualization)
|
||||
4. **Images**: Coil library for async loading
|
||||
5. **Build**: ProGuard for release builds
|
||||
|
||||
### Memory Management
|
||||
- Singleton database pattern
|
||||
- ViewModel lifecycle awareness
|
||||
- Proper coroutine scoping
|
||||
- WebView cleanup on destroy
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
### Permissions Required
|
||||
- `INTERNET` - For loading PWAs
|
||||
- `ACCESS_NETWORK_STATE` - Network detection
|
||||
- `POST_NOTIFICATIONS` - PWA push notifications
|
||||
- Storage (SDK < 33) - Offline caching
|
||||
|
||||
### ProGuard Rules
|
||||
- Keep WebView JavaScript interfaces
|
||||
- Keep Room entities
|
||||
- Keep serialization classes
|
||||
- Protect Compose internals
|
||||
|
||||
## 🎯 Feature Parity with SilverPOWERSHELL
|
||||
|
||||
| Feature | SilverPOWERSHELL | SilverDROID |
|
||||
|---------|------------------|-------------|
|
||||
| Modern UI Framework | Svelte 5 | Jetpack Compose |
|
||||
| Glass Effects | CSS blur/transparency | Compose graphics |
|
||||
| Native Container | Electron | Android Native |
|
||||
| Web Engine | Chromium (Electron) | Chromium (WebView) |
|
||||
| Offline Support | ✅ | ✅ |
|
||||
| App Management | File system | Room Database |
|
||||
| Launcher Grid | ❌ | ✅ |
|
||||
| WASM Support | ✅ | ✅ |
|
||||
|
||||
## 🔮 Future Enhancements
|
||||
|
||||
### Planned Features (Not Yet Implemented)
|
||||
1. **Manifest Parsing**
|
||||
- Auto-fetch PWA manifest.json
|
||||
- Extract icons, colors, display mode
|
||||
- Parse capabilities
|
||||
|
||||
2. **Icon Management**
|
||||
- Download and cache app icons
|
||||
- Generate fallback icons
|
||||
- Icon pack support
|
||||
|
||||
3. **Settings Screen**
|
||||
- Theme selection
|
||||
- Default display mode
|
||||
- Cache management
|
||||
- Debug options
|
||||
|
||||
4. **Categories/Tags**
|
||||
- Custom app categorization
|
||||
- Filter by tags
|
||||
- Sort options
|
||||
|
||||
5. **Widgets**
|
||||
- Quick launch widgets
|
||||
- App shortcuts
|
||||
- Recent apps widget
|
||||
|
||||
6. **Notifications**
|
||||
- PWA push notification forwarding
|
||||
- Notification channels
|
||||
- Notification settings
|
||||
|
||||
7. **Search**
|
||||
- Search installed apps
|
||||
- Quick launch by name
|
||||
- Recent searches
|
||||
|
||||
8. **Backup/Restore**
|
||||
- Export app list
|
||||
- Cloud backup
|
||||
- Import from JSON
|
||||
|
||||
## 🔧 Build System
|
||||
|
||||
### Gradle Configuration
|
||||
- **Kotlin DSL**: Modern type-safe build scripts
|
||||
- **Version Catalog**: Centralized dependency management
|
||||
- **KSP**: Kotlin Symbol Processing for Room
|
||||
- **Build Cache**: Faster subsequent builds
|
||||
- **Configuration Cache**: Gradle optimization
|
||||
|
||||
### Build Variants
|
||||
- **Debug**: Fast iteration, WebView debugging
|
||||
- **Release**: Optimized, obfuscated, signed
|
||||
|
||||
## 📱 Minimum Requirements
|
||||
|
||||
- **Min SDK**: 26 (Android 8.0 Oreo)
|
||||
- **Target SDK**: 35 (Android 15)
|
||||
- **Compile SDK**: 35
|
||||
- **JDK**: 17
|
||||
- **Gradle**: 8.9
|
||||
- **Kotlin**: 2.1.0
|
||||
|
||||
## 🎓 Learning from SilverPOWERSHELL
|
||||
|
||||
### Adapted Concepts
|
||||
1. **Glass UI Components** → Compose equivalents
|
||||
2. **Sidebar/Panel Layout** → Top bar + FAB
|
||||
3. **Terminal Integration** → WebView container
|
||||
4. **Plugin System** → PWA apps as "plugins"
|
||||
5. **Project Context** → App metadata in database
|
||||
|
||||
### Key Differences
|
||||
- **Platform**: Desktop (Electron) vs Mobile (Android)
|
||||
- **Architecture**: Node.js + Svelte vs Kotlin + Compose
|
||||
- **Distribution**: Standalone exe vs APK/AAB
|
||||
- **Use Case**: Developer tool vs App launcher
|
||||
|
||||
## ✅ Ready for Development
|
||||
|
||||
The project is now fully scaffolded and ready for:
|
||||
1. Opening in Android Studio
|
||||
2. Building (debug/release)
|
||||
3. Running on device/emulator
|
||||
4. Adding new features
|
||||
5. GitLab integration
|
||||
|
||||
### Next Steps for Developer
|
||||
1. Open project in Android Studio
|
||||
2. Sync Gradle dependencies
|
||||
3. Create AVD or connect device
|
||||
4. Run the app (▶️ button)
|
||||
5. Test adding a PWA (e.g., twitter.com)
|
||||
6. Explore glassmorphism effects
|
||||
|
||||
---
|
||||
|
||||
**Built with ❤️ by SilverLABS**
|
||||
*Inspired by SilverPOWERSHELL's Electron + Svelte architecture*
|
||||
204
QUICKSTART.md
Normal file
204
QUICKSTART.md
Normal file
@@ -0,0 +1,204 @@
|
||||
# SilverDROID - Quick Start Guide
|
||||
|
||||
## 🚀 5-Minute Setup
|
||||
|
||||
### Option 1: Android Studio (Recommended)
|
||||
|
||||
1. **Open Project**
|
||||
```bash
|
||||
# From WSL
|
||||
cd /mnt/c/Production/Source/SilverLABS/SilverDROID
|
||||
```
|
||||
- Launch Android Studio
|
||||
- Click "Open"
|
||||
- Navigate to `SilverDROID` folder
|
||||
- Click "OK"
|
||||
|
||||
2. **Wait for Sync**
|
||||
- Gradle will sync automatically (~2-5 minutes first time)
|
||||
- Status shown in bottom status bar
|
||||
|
||||
3. **Run the App**
|
||||
- Click the green ▶️ (Run) button
|
||||
- Select your device/emulator
|
||||
- App will build and install
|
||||
|
||||
### Option 2: Command Line
|
||||
|
||||
```bash
|
||||
cd /mnt/c/Production/Source/SilverLABS/SilverDROID
|
||||
|
||||
# Build and install
|
||||
./gradlew installDebug
|
||||
|
||||
# Or build only
|
||||
./gradlew assembleDebug
|
||||
```
|
||||
|
||||
## 📱 First Launch
|
||||
|
||||
### What You'll See
|
||||
1. **Glassmorphism Background** - Gradient with frosted glass effect
|
||||
2. **"No Apps Yet" Screen** - Empty state with add button
|
||||
3. **+ Button** - Floating action button (bottom-right)
|
||||
|
||||
### Add Your First App
|
||||
|
||||
1. **Tap the + Button**
|
||||
2. **Enter a URL**
|
||||
- Try: `https://mobile.twitter.com`
|
||||
- Or: `https://m.youtube.com`
|
||||
- Or: `https://webassembly.org/demo/`
|
||||
3. **Tap "Add"**
|
||||
4. **See Your App** - Glass card appears in grid
|
||||
5. **Tap the Card** - Opens in full-screen WebView
|
||||
|
||||
## 🎨 What Makes It Special?
|
||||
|
||||
### Glassmorphism Effects
|
||||
- **Frosted Glass Cards** - Semi-transparent app cards
|
||||
- **Blur Effects** - Background blur on panels
|
||||
- **Gradient Background** - Animated color gradient
|
||||
- **Border Glow** - Subtle borders on glass elements
|
||||
|
||||
### PWA/WASM Features
|
||||
- **Offline Support** - Apps work without internet
|
||||
- **Service Workers** - Background sync
|
||||
- **Full-Screen Mode** - Immersive app experience
|
||||
- **WASM Execution** - Fast WebAssembly performance
|
||||
|
||||
## 🔧 Development Tips
|
||||
|
||||
### Hot Reload (Sort of)
|
||||
Compose supports limited hot reload:
|
||||
1. Make UI changes in `.kt` files
|
||||
2. Click ⚡ "Apply Changes" button
|
||||
3. Some changes apply without rebuild
|
||||
|
||||
### WebView Debugging
|
||||
1. Open Chrome on your PC
|
||||
2. Navigate to `chrome://inspect`
|
||||
3. Find "SilverDROID" under "Remote Target"
|
||||
4. Click "inspect"
|
||||
5. Full DevTools for PWA debugging!
|
||||
|
||||
### Database Inspection
|
||||
```bash
|
||||
# Pull database from device
|
||||
adb pull /data/data/uk.silverlabs.silverdroid/databases/pwa_database ./
|
||||
|
||||
# Open with SQLite browser
|
||||
sqlite3 pwa_database
|
||||
.tables
|
||||
SELECT * FROM pwa_apps;
|
||||
```
|
||||
|
||||
## 📝 Sample Apps to Try
|
||||
|
||||
### PWAs
|
||||
- **Twitter**: `https://mobile.twitter.com`
|
||||
- **Spotify**: `https://open.spotify.com`
|
||||
- **YouTube**: `https://m.youtube.com`
|
||||
- **Instagram**: `https://www.instagram.com`
|
||||
- **Notion**: `https://www.notion.so`
|
||||
|
||||
### WASM Demos
|
||||
- **WebAssembly.org Demo**: `https://webassembly.org/demo/`
|
||||
- **Figma**: `https://www.figma.com` (uses WASM)
|
||||
- **Google Earth**: `https://earth.google.com/web/`
|
||||
- **Photopea**: `https://www.photopea.com` (Photoshop in browser)
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### "Gradle Sync Failed"
|
||||
```bash
|
||||
# Clean and rebuild
|
||||
./gradlew clean build
|
||||
```
|
||||
|
||||
### "Device Not Found"
|
||||
```bash
|
||||
# Check connected devices
|
||||
adb devices
|
||||
|
||||
# If empty, enable USB debugging on your Android device:
|
||||
# Settings → About Phone → Tap "Build Number" 7 times
|
||||
# Settings → Developer Options → Enable "USB Debugging"
|
||||
```
|
||||
|
||||
### "Build Failed"
|
||||
1. Check JDK version: `java -version` (must be 17+)
|
||||
2. Update Android Studio to latest
|
||||
3. Invalidate caches: File → Invalidate Caches / Restart
|
||||
|
||||
### WebView Not Loading
|
||||
- Check internet connection
|
||||
- Verify URL starts with `https://`
|
||||
- Try a different URL
|
||||
- Check device logs: `adb logcat | grep SilverDROID`
|
||||
|
||||
## 🎯 Project Structure (Quick Reference)
|
||||
|
||||
```
|
||||
SilverDROID/
|
||||
├── app/src/main/kotlin/uk/silverlabs/silverdroid/
|
||||
│ ├── MainActivity.kt # Entry point
|
||||
│ ├── ui/launcher/ # Main screen
|
||||
│ ├── ui/webview/ # PWA container
|
||||
│ ├── ui/components/ # Glass components
|
||||
│ ├── ui/theme/ # Theming
|
||||
│ └── data/ # Database
|
||||
├── build.gradle.kts # Build config
|
||||
└── README.md # Full docs
|
||||
```
|
||||
|
||||
## 📚 Next Steps
|
||||
|
||||
1. **Customize Colors**
|
||||
- Edit `ui/theme/Color.kt`
|
||||
- Change glass tint/opacity
|
||||
- Adjust Material Design colors
|
||||
|
||||
2. **Add Features**
|
||||
- Settings screen
|
||||
- Icon caching
|
||||
- Categories/tags
|
||||
- Search functionality
|
||||
|
||||
3. **Deploy**
|
||||
- Build release APK: `./gradlew assembleRelease`
|
||||
- Sign with your keystore
|
||||
- Distribute or publish
|
||||
|
||||
## 💡 Pro Tips
|
||||
|
||||
### Fast Iteration
|
||||
1. Keep Android Studio open
|
||||
2. Make changes in `.kt` files
|
||||
3. Use "Apply Changes" (⚡) instead of full rebuild
|
||||
4. Test on real device for better performance
|
||||
|
||||
### Glass Effects
|
||||
- Adjust blur radius in `GlassComponents.kt`
|
||||
- Modify alpha values for transparency
|
||||
- Experiment with gradient colors in `GlassBackground`
|
||||
|
||||
### Performance
|
||||
- Test on real devices (emulator is slower)
|
||||
- Profile with Android Profiler
|
||||
- Monitor WebView memory usage
|
||||
- Optimize large app lists (already using LazyGrid)
|
||||
|
||||
---
|
||||
|
||||
## 🎉 That's It!
|
||||
|
||||
You now have a working Android PWA/WASM launcher with glassmorphism UI!
|
||||
|
||||
**Questions?** Check the full README.md or PROJECT_SUMMARY.md
|
||||
|
||||
**Issues?** https://gitlab.silverlabs.uk/SilverLABS/silverdroid/issues
|
||||
|
||||
---
|
||||
|
||||
**Built with ❤️ by SilverLABS**
|
||||
163
QUICK_REFERENCE.md
Normal file
163
QUICK_REFERENCE.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# SilverDROID - Quick Reference Card
|
||||
|
||||
## 🚀 Push to GitLab (First Time)
|
||||
|
||||
```bash
|
||||
# WSL/Linux
|
||||
./push-to-gitlab.sh
|
||||
|
||||
# Windows PowerShell
|
||||
.\push-to-gitlab.ps1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Build Locally
|
||||
|
||||
```bash
|
||||
# Debug APK
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Release APK
|
||||
./gradlew assembleRelease
|
||||
|
||||
# Install on device
|
||||
adb install app/build/outputs/apk/debug/app-debug.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 GitLab URLs
|
||||
|
||||
**Project:** https://gitlab.silverlabs.uk/SilverLABS/silverdroid
|
||||
|
||||
**Pipelines:** https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/pipelines
|
||||
|
||||
**Jobs:** https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/jobs
|
||||
|
||||
**Latest Debug APK:**
|
||||
```
|
||||
https://gitlab.silverlabs.uk/SilverLABS/silverdroid/-/jobs/artifacts/main/raw/app/build/outputs/apk/debug/app-debug.apk?job=build:debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 CI/CD Pipeline
|
||||
|
||||
**Trigger:** Push to any branch
|
||||
|
||||
**Stages:**
|
||||
1. prepare (1 min) - Cache dependencies
|
||||
2. test (2 min) - Lint + unit tests
|
||||
3. build (3-5 min) - Build APKs
|
||||
4. deploy (30 sec) - Store artifacts
|
||||
|
||||
**Total:** ~5-8 minutes
|
||||
|
||||
---
|
||||
|
||||
## 🎯 What the App Does
|
||||
|
||||
- Loads **https://admin.dark.side** on launch
|
||||
- Full **WASM/PWA** support
|
||||
- **Glassmorphism** UI theme
|
||||
- Back button exits app
|
||||
|
||||
---
|
||||
|
||||
## 📱 App Info
|
||||
|
||||
**Package:** uk.silverlabs.silverdroid
|
||||
**Name:** Dark Side Admin
|
||||
**Min Android:** 8.0 (API 26)
|
||||
**Target:** Android 15 (API 35)
|
||||
|
||||
---
|
||||
|
||||
## 🔑 GitLab Access
|
||||
|
||||
**URL:** https://gitlab.silverlabs.uk
|
||||
**Token:** glpat-wqUcD7mg53F1mgM-N-PdiW86MQp1OjEH.01.0w074ox93
|
||||
**SSH Port:** 2223
|
||||
|
||||
---
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| **README.md** | Full project docs |
|
||||
| **DARK_SIDE_BUILD.md** | Custom build guide |
|
||||
| **GITLAB_CICD_SETUP.md** | CI/CD setup |
|
||||
| **CICD_SUMMARY.md** | Pipeline overview |
|
||||
| **BUILD.md** | Build instructions |
|
||||
| **QUICKSTART.md** | 5-min getting started |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Key Files
|
||||
|
||||
```
|
||||
MainActivity.kt - Loads admin.dark.side
|
||||
WasmWebView.kt - WebView with WASM support
|
||||
GlassComponents.kt - Glassmorphism UI
|
||||
.gitlab-ci.yml - CI/CD pipeline
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ Quick Commands
|
||||
|
||||
```bash
|
||||
# Build debug
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Run tests
|
||||
./gradlew test
|
||||
|
||||
# Lint check
|
||||
./gradlew lint
|
||||
|
||||
# Clean build
|
||||
./gradlew clean build
|
||||
|
||||
# Install on device
|
||||
./gradlew installDebug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Customize Target URL
|
||||
|
||||
Edit `MainActivity.kt` line 23:
|
||||
```kotlin
|
||||
private val targetUrl = "https://your-url-here"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
**Can't build?**
|
||||
- Open in Android Studio
|
||||
- File → Sync Project with Gradle Files
|
||||
|
||||
**Pipeline not starting?**
|
||||
- Check Runner: Settings → CI/CD → Runners
|
||||
- Verify `.gitlab-ci.yml` exists
|
||||
|
||||
**APK won't install?**
|
||||
- Enable "Unknown Sources" on device
|
||||
- Settings → Security → Unknown Sources
|
||||
|
||||
---
|
||||
|
||||
## 📞 Support
|
||||
|
||||
**GitLab:** https://gitlab.silverlabs.uk
|
||||
**TeamCity:** https://cis1.silverlabs.uk
|
||||
**Knowledge:** ~/.claude/Knowledge/
|
||||
|
||||
---
|
||||
|
||||
**Next Step:** Run `./push-to-gitlab.sh` to get started! 🚀
|
||||
222
README.md
Normal file
222
README.md
Normal file
@@ -0,0 +1,222 @@
|
||||
# SilverDROID
|
||||
|
||||
**Android WASM/PWA Launcher with Glassmorphism UI**
|
||||
|
||||
A modern Android launcher application for Progressive Web Apps (PWAs) and WebAssembly (WASM) applications, featuring a beautiful glassmorphism design inspired by SilverPOWERSHELL.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
### Core Functionality
|
||||
- 🚀 **PWA Launcher** - Install and manage Progressive Web Apps
|
||||
- 🌐 **WASM Support** - Full WebAssembly support via optimized WebView
|
||||
- 📱 **Native Container** - PWAs run in a native Android wrapper
|
||||
- 💾 **Offline Support** - Apps work offline with cached resources
|
||||
- 🔔 **Notifications** - PWA push notification forwarding
|
||||
|
||||
### Beautiful UI
|
||||
- ✨ **Glassmorphism Design** - Frosted glass effects throughout
|
||||
- 🎨 **Material Design 3** - Modern Android design language
|
||||
- 🌓 **Dynamic Theming** - Adapts to system theme (light/dark)
|
||||
- 🖼️ **App Grid** - Beautiful card-based launcher grid
|
||||
- 💫 **Smooth Animations** - Fluid transitions and effects
|
||||
|
||||
### Technical
|
||||
- ⚡ **Jetpack Compose** - Modern declarative UI
|
||||
- 🏗️ **Clean Architecture** - MVVM + Repository pattern
|
||||
- 💿 **Room Database** - Efficient local app storage
|
||||
- 🔧 **WebView Optimization** - Enhanced for WASM/PWA performance
|
||||
|
||||
## 🎯 Technology Stack
|
||||
|
||||
- **Language:** Kotlin
|
||||
- **UI Framework:** Jetpack Compose + Material Design 3
|
||||
- **WebView:** Android System WebView (Chromium)
|
||||
- **Database:** Room Persistence Library
|
||||
- **Architecture:** MVVM with Clean Architecture
|
||||
- **Min SDK:** 26 (Android 8.0)
|
||||
- **Target SDK:** 35 (Android 15)
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
```
|
||||
SilverDROID/
|
||||
├── ui/
|
||||
│ ├── launcher/ # Main launcher screen
|
||||
│ │ ├── LauncherScreen.kt
|
||||
│ │ └── LauncherViewModel.kt
|
||||
│ ├── webview/ # PWA container
|
||||
│ │ ├── WebViewActivity.kt
|
||||
│ │ └── WasmWebView.kt
|
||||
│ ├── components/ # Reusable glass components
|
||||
│ │ └── GlassComponents.kt
|
||||
│ └── theme/ # Material Design 3 theme
|
||||
│ ├── Color.kt
|
||||
│ ├── Theme.kt
|
||||
│ └── Type.kt
|
||||
├── data/
|
||||
│ ├── model/ # Data models
|
||||
│ │ └── PwaApp.kt
|
||||
│ ├── repository/ # Data repositories
|
||||
│ │ └── PwaRepository.kt
|
||||
│ ├── PwaDatabase.kt # Room database
|
||||
│ └── PwaAppDao.kt # DAO interface
|
||||
└── MainActivity.kt # App entry point
|
||||
```
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Prerequisites
|
||||
- Android Studio Ladybug (2024.2.1) or later
|
||||
- JDK 17 or later
|
||||
- Android SDK 26+
|
||||
|
||||
### Building the Project
|
||||
|
||||
1. **Clone the repository**
|
||||
```bash
|
||||
git clone https://gitlab.silverlabs.uk/SilverLABS/silverdroid.git
|
||||
cd silverdroid
|
||||
```
|
||||
|
||||
2. **Open in Android Studio**
|
||||
- Open Android Studio
|
||||
- File → Open → Select the `SilverDROID` folder
|
||||
|
||||
3. **Sync Gradle**
|
||||
- Android Studio will automatically sync Gradle dependencies
|
||||
- Wait for the sync to complete
|
||||
|
||||
4. **Run the app**
|
||||
- Connect an Android device or start an emulator
|
||||
- Click the "Run" button (▶️) in Android Studio
|
||||
|
||||
### Quick Start Commands
|
||||
|
||||
```bash
|
||||
# Build debug APK
|
||||
./gradlew assembleDebug
|
||||
|
||||
# Build release APK
|
||||
./gradlew assembleRelease
|
||||
|
||||
# Install on connected device
|
||||
./gradlew installDebug
|
||||
|
||||
# Run tests
|
||||
./gradlew test
|
||||
```
|
||||
|
||||
## 📦 Building for Production
|
||||
|
||||
1. **Generate a signing key** (first time only)
|
||||
```bash
|
||||
keytool -genkey -v -keystore silverdroid.keystore \
|
||||
-alias silverdroid -keyalg RSA -keysize 2048 -validity 10000
|
||||
```
|
||||
|
||||
2. **Configure signing in `app/build.gradle.kts`**
|
||||
```kotlin
|
||||
android {
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
storeFile = file("../silverdroid.keystore")
|
||||
storePassword = "your_password"
|
||||
keyAlias = "silverdroid"
|
||||
keyPassword = "your_password"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. **Build release APK**
|
||||
```bash
|
||||
./gradlew assembleRelease
|
||||
```
|
||||
|
||||
4. **Output location**
|
||||
```
|
||||
app/build/outputs/apk/release/app-release.apk
|
||||
```
|
||||
|
||||
## 🎨 Glassmorphism Components
|
||||
|
||||
SilverDROID includes custom glassmorphism components:
|
||||
|
||||
### GlassCard
|
||||
```kotlin
|
||||
GlassCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
cornerRadius = 16.dp
|
||||
) {
|
||||
Text("Frosted glass card")
|
||||
}
|
||||
```
|
||||
|
||||
### FrostedGlassPanel
|
||||
```kotlin
|
||||
FrostedGlassPanel(
|
||||
cornerRadius = 20.dp
|
||||
) {
|
||||
// Your content
|
||||
}
|
||||
```
|
||||
|
||||
### GlassBackground
|
||||
```kotlin
|
||||
GlassBackground() // Gradient background
|
||||
```
|
||||
|
||||
## 📱 Adding Apps
|
||||
|
||||
### Manually
|
||||
1. Tap the **+** button
|
||||
2. Enter the PWA URL (e.g., `https://twitter.com`)
|
||||
3. Optionally enter a custom name
|
||||
4. Tap **Add**
|
||||
|
||||
### Sample Apps
|
||||
The app includes sample PWAs you can add:
|
||||
- Twitter (mobile.twitter.com)
|
||||
- Spotify (open.spotify.com)
|
||||
- YouTube (m.youtube.com)
|
||||
- WebAssembly Demo (webassembly.org/demo)
|
||||
|
||||
## 🔧 WebView Configuration
|
||||
|
||||
SilverDROID's WebView is optimized for PWA/WASM:
|
||||
|
||||
- ✅ JavaScript enabled
|
||||
- ✅ DOM Storage enabled
|
||||
- ✅ Database storage enabled
|
||||
- ✅ App cache enabled
|
||||
- ✅ Mixed content allowed (dev)
|
||||
- ✅ File access enabled
|
||||
- ✅ Wide viewport support
|
||||
- ✅ WebAssembly support
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
Contributions are welcome! This is a SilverLABS project.
|
||||
|
||||
## 📄 License
|
||||
|
||||
Copyright © 2025 SilverLABS. All rights reserved.
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
For issues or questions:
|
||||
- GitLab Issues: https://gitlab.silverlabs.uk/SilverLABS/silverdroid/issues
|
||||
- SilverLABS Infrastructure: See `~/.claude/Knowledge/`
|
||||
|
||||
## 🎉 Credits
|
||||
|
||||
Built with ❤️ by SilverLABS
|
||||
|
||||
Inspired by:
|
||||
- **SilverPOWERSHELL** - Electron + Svelte architecture
|
||||
- **Material Design 3** - Google's design system
|
||||
- **Glassmorphism** - Modern UI trend
|
||||
|
||||
---
|
||||
|
||||
**Note:** This launcher is designed for PWAs and WASM apps. Regular Android apps from Google Play Store are not supported.
|
||||
104
app/build.gradle.kts
Normal file
104
app/build.gradle.kts
Normal file
@@ -0,0 +1,104 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
id("com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "uk.silverlabs.silverdroid"
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "uk.silverlabs.silverdroid"
|
||||
minSdk = 26
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
versionName = "1.0.0"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Core Android
|
||||
implementation("androidx.core:core-ktx:1.15.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.7")
|
||||
implementation("androidx.activity:activity-compose:1.9.3")
|
||||
|
||||
// Compose BOM and UI
|
||||
implementation(platform("androidx.compose:compose-bom:2025.01.00"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-graphics")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.compose.material3:material3")
|
||||
implementation("androidx.compose.material:material-icons-extended")
|
||||
|
||||
// Compose Navigation
|
||||
implementation("androidx.navigation:navigation-compose:2.8.5")
|
||||
|
||||
// WebView
|
||||
implementation("androidx.webkit:webkit:1.12.1")
|
||||
|
||||
// Room Database
|
||||
implementation("androidx.room:room-runtime:2.6.1")
|
||||
implementation("androidx.room:room-ktx:2.6.1")
|
||||
ksp("androidx.room:room-compiler:2.6.1")
|
||||
|
||||
// Coroutines
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1")
|
||||
|
||||
// DataStore (for preferences)
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.1")
|
||||
|
||||
// JSON parsing
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.8.0")
|
||||
|
||||
// Coil for image loading
|
||||
implementation("io.coil-kt.coil3:coil-compose:3.0.4")
|
||||
implementation("io.coil-kt.coil3:coil-network-okhttp:3.0.4")
|
||||
|
||||
// Blur effect library
|
||||
implementation("com.github.Dimezis:BlurView:version-2.0.5")
|
||||
|
||||
// Testing
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.2.1")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2025.01.00"))
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
}
|
||||
23
app/proguard-rules.pro
vendored
Normal file
23
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# Keep WebView JavaScript interface
|
||||
-keepclassmembers class * {
|
||||
@android.webkit.JavascriptInterface <methods>;
|
||||
}
|
||||
|
||||
# Keep Room entities
|
||||
-keep class uk.silverlabs.silverdroid.data.model.** { *; }
|
||||
|
||||
# Keep serialization
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.AnnotationsKt
|
||||
|
||||
-keepclassmembers class kotlinx.serialization.json.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class kotlinx.serialization.json.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Keep Compose
|
||||
-keep class androidx.compose.** { *; }
|
||||
-keep class kotlin.Metadata { *; }
|
||||
54
app/src/main/AndroidManifest.xml
Normal file
54
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<!-- Internet permission for WebView and PWAs -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<!-- Storage permissions for PWA offline caching -->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="ScopedStorage" />
|
||||
|
||||
<!-- Notifications for PWA push notifications -->
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.SilverDROID"
|
||||
android:hardwareAccelerated="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:targetApi="35">
|
||||
|
||||
<!-- Main Launcher Activity -->
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.SilverDROID"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- WebView Activity for PWAs -->
|
||||
<activity
|
||||
android:name=".ui.webview.WebViewActivity"
|
||||
android:exported="false"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||
android:theme="@style/Theme.SilverDROID.Fullscreen"
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,49 @@
|
||||
package uk.silverlabs.silverdroid
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.ui.Modifier
|
||||
import uk.silverlabs.silverdroid.ui.theme.SilverDROIDTheme
|
||||
import uk.silverlabs.silverdroid.ui.webview.WasmWebView
|
||||
|
||||
/**
|
||||
* SilverDROID - Direct Load Version
|
||||
*
|
||||
* This version loads https://admin.dark.side directly on launch,
|
||||
* bypassing the launcher screen.
|
||||
*/
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
// Direct load configuration
|
||||
private val targetUrl = "https://admin.dark.side"
|
||||
private val appName = "Dark Side Admin"
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
// Load admin.dark.side directly
|
||||
setContent {
|
||||
SilverDROIDTheme {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
WasmWebView(
|
||||
url = targetUrl,
|
||||
appName = appName,
|
||||
onBackPressed = {
|
||||
// Exit app on back press
|
||||
finish()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package uk.silverlabs.silverdroid.data
|
||||
|
||||
import androidx.room.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import uk.silverlabs.silverdroid.data.model.PwaApp
|
||||
|
||||
@Dao
|
||||
interface PwaAppDao {
|
||||
@Query("SELECT * FROM pwa_apps ORDER BY sortOrder ASC, lastAccessed DESC")
|
||||
fun getAllApps(): Flow<List<PwaApp>>
|
||||
|
||||
@Query("SELECT * FROM pwa_apps WHERE id = :id")
|
||||
suspend fun getAppById(id: Long): PwaApp?
|
||||
|
||||
@Query("SELECT * FROM pwa_apps WHERE url = :url LIMIT 1")
|
||||
suspend fun getAppByUrl(url: String): PwaApp?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
suspend fun insertApp(app: PwaApp): Long
|
||||
|
||||
@Update
|
||||
suspend fun updateApp(app: PwaApp)
|
||||
|
||||
@Delete
|
||||
suspend fun deleteApp(app: PwaApp)
|
||||
|
||||
@Query("UPDATE pwa_apps SET lastAccessed = :timestamp WHERE id = :id")
|
||||
suspend fun updateLastAccessed(id: Long, timestamp: Long = System.currentTimeMillis())
|
||||
|
||||
@Query("DELETE FROM pwa_apps WHERE id = :id")
|
||||
suspend fun deleteAppById(id: Long)
|
||||
|
||||
@Query("SELECT COUNT(*) FROM pwa_apps")
|
||||
suspend fun getAppCount(): Int
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package uk.silverlabs.silverdroid.data
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import uk.silverlabs.silverdroid.data.model.PwaApp
|
||||
|
||||
@Database(
|
||||
entities = [PwaApp::class],
|
||||
version = 1,
|
||||
exportSchema = true
|
||||
)
|
||||
abstract class PwaDatabase : RoomDatabase() {
|
||||
abstract fun pwaAppDao(): PwaAppDao
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: PwaDatabase? = null
|
||||
|
||||
fun getInstance(context: Context): PwaDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
val instance = Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
PwaDatabase::class.java,
|
||||
"pwa_database"
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
INSTANCE = instance
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package uk.silverlabs.silverdroid.data.model
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
/**
|
||||
* Represents a Progressive Web App or WASM app installed in the launcher
|
||||
*/
|
||||
@Entity(tableName = "pwa_apps")
|
||||
data class PwaApp(
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
val id: Long = 0,
|
||||
|
||||
/** Display name of the app */
|
||||
val name: String,
|
||||
|
||||
/** URL of the PWA/WASM app */
|
||||
val url: String,
|
||||
|
||||
/** Icon URL or local path */
|
||||
val iconUrl: String? = null,
|
||||
|
||||
/** Short description */
|
||||
val description: String? = null,
|
||||
|
||||
/** Theme color from manifest */
|
||||
val themeColor: String? = null,
|
||||
|
||||
/** Background color from manifest */
|
||||
val backgroundColor: String? = null,
|
||||
|
||||
/** Whether the app is installed (from manifest) */
|
||||
val isInstalled: Boolean = true,
|
||||
|
||||
/** Last accessed timestamp */
|
||||
val lastAccessed: Long = System.currentTimeMillis(),
|
||||
|
||||
/** Installation timestamp */
|
||||
val installedAt: Long = System.currentTimeMillis(),
|
||||
|
||||
/** Display mode: standalone, fullscreen, minimal-ui, browser */
|
||||
val displayMode: String = "standalone",
|
||||
|
||||
/** Manifest JSON (serialized) */
|
||||
val manifestJson: String? = null,
|
||||
|
||||
/** Is this a WASM-specific app */
|
||||
val isWasmApp: Boolean = false,
|
||||
|
||||
/** Custom tags for categorization */
|
||||
val tags: String? = null,
|
||||
|
||||
/** Sort order */
|
||||
val sortOrder: Int = 0
|
||||
)
|
||||
@@ -0,0 +1,88 @@
|
||||
package uk.silverlabs.silverdroid.data.repository
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import uk.silverlabs.silverdroid.data.PwaAppDao
|
||||
import uk.silverlabs.silverdroid.data.model.PwaApp
|
||||
|
||||
class PwaRepository(private val pwaAppDao: PwaAppDao) {
|
||||
|
||||
val allApps: Flow<List<PwaApp>> = pwaAppDao.getAllApps()
|
||||
|
||||
suspend fun getAppById(id: Long): PwaApp? {
|
||||
return pwaAppDao.getAppById(id)
|
||||
}
|
||||
|
||||
suspend fun getAppByUrl(url: String): PwaApp? {
|
||||
return pwaAppDao.getAppByUrl(url)
|
||||
}
|
||||
|
||||
suspend fun insertApp(app: PwaApp): Long {
|
||||
return pwaAppDao.insertApp(app)
|
||||
}
|
||||
|
||||
suspend fun updateApp(app: PwaApp) {
|
||||
pwaAppDao.updateApp(app)
|
||||
}
|
||||
|
||||
suspend fun deleteApp(app: PwaApp) {
|
||||
pwaAppDao.deleteApp(app)
|
||||
}
|
||||
|
||||
suspend fun deleteAppById(id: Long) {
|
||||
pwaAppDao.deleteAppById(id)
|
||||
}
|
||||
|
||||
suspend fun updateLastAccessed(id: Long) {
|
||||
pwaAppDao.updateLastAccessed(id)
|
||||
}
|
||||
|
||||
suspend fun getAppCount(): Int {
|
||||
return pwaAppDao.getAppCount()
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a PWA from URL by fetching its manifest
|
||||
*/
|
||||
suspend fun installPwaFromUrl(
|
||||
url: String,
|
||||
name: String? = null,
|
||||
iconUrl: String? = null
|
||||
): Result<Long> {
|
||||
return try {
|
||||
// Check if already installed
|
||||
val existing = getAppByUrl(url)
|
||||
if (existing != null) {
|
||||
return Result.failure(Exception("App already installed"))
|
||||
}
|
||||
|
||||
val app = PwaApp(
|
||||
name = name ?: extractDomainName(url),
|
||||
url = url,
|
||||
iconUrl = iconUrl,
|
||||
isInstalled = true,
|
||||
installedAt = System.currentTimeMillis(),
|
||||
lastAccessed = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
val id = insertApp(app)
|
||||
Result.success(id)
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractDomainName(url: String): String {
|
||||
return try {
|
||||
val domain = url.substringAfter("://")
|
||||
.substringBefore("/")
|
||||
.substringBefore(":")
|
||||
domain.substringAfter("www.")
|
||||
.split(".")
|
||||
.firstOrNull()
|
||||
?.replaceFirstChar { it.uppercase() }
|
||||
?: "App"
|
||||
} catch (e: Exception) {
|
||||
"App"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package uk.silverlabs.silverdroid.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import uk.silverlabs.silverdroid.ui.theme.GlassColors
|
||||
|
||||
/**
|
||||
* Glassmorphism card component with blur and transparency effects
|
||||
*/
|
||||
@Composable
|
||||
fun GlassCard(
|
||||
modifier: Modifier = Modifier,
|
||||
cornerRadius: Dp = 16.dp,
|
||||
borderWidth: Dp = 1.dp,
|
||||
blurRadius: Dp = 10.dp,
|
||||
content: @Composable BoxScope.() -> Unit
|
||||
) {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
|
||||
val glassSurface = if (isDark) GlassColors.GlassSurfaceDark else GlassColors.GlassSurfaceLight
|
||||
val glassBorder = if (isDark) GlassColors.GlassBorderDark else GlassColors.GlassBorderLight
|
||||
|
||||
val shape = RoundedCornerShape(cornerRadius)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(shape)
|
||||
.background(glassSurface)
|
||||
.border(
|
||||
width = borderWidth,
|
||||
color = glassBorder,
|
||||
shape = shape
|
||||
)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Animated gradient glass background
|
||||
*/
|
||||
@Composable
|
||||
fun GlassBackground(
|
||||
modifier: Modifier = Modifier,
|
||||
darkTheme: Boolean = isSystemInDarkTheme()
|
||||
) {
|
||||
val gradientColors = if (darkTheme) {
|
||||
listOf(
|
||||
Color(0xFF0F0C29),
|
||||
Color(0xFF302B63),
|
||||
Color(0xFF24243E)
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
Color(0xFFE0E7FF),
|
||||
Color(0xFFF5F3FF),
|
||||
Color(0xFFFFE4E6)
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
brush = Brush.verticalGradient(
|
||||
colors = gradientColors
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Frosted glass panel with elevated blur effect
|
||||
*/
|
||||
@Composable
|
||||
fun FrostedGlassPanel(
|
||||
modifier: Modifier = Modifier,
|
||||
cornerRadius: Dp = 20.dp,
|
||||
content: @Composable BoxScope.() -> Unit
|
||||
) {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
|
||||
val glassSurface = if (isDark) {
|
||||
Color(0xBB1C1B1F)
|
||||
} else {
|
||||
Color(0xDDFFFFFF)
|
||||
}
|
||||
|
||||
val glassBorder = if (isDark) {
|
||||
Color(0x80FFFFFF)
|
||||
} else {
|
||||
Color(0x40000000)
|
||||
}
|
||||
|
||||
val shape = RoundedCornerShape(cornerRadius)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(shape)
|
||||
.background(glassSurface)
|
||||
.border(
|
||||
width = 1.5.dp,
|
||||
brush = Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
glassBorder.copy(alpha = 0.8f),
|
||||
glassBorder.copy(alpha = 0.2f)
|
||||
)
|
||||
),
|
||||
shape = shape
|
||||
)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Glass surface with subtle shimmer effect
|
||||
*/
|
||||
@Composable
|
||||
fun ShimmerGlass(
|
||||
modifier: Modifier = Modifier,
|
||||
cornerRadius: Dp = 12.dp,
|
||||
content: @Composable BoxScope.() -> Unit
|
||||
) {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
|
||||
val shimmerColors = if (isDark) {
|
||||
listOf(
|
||||
GlassColors.GlassSurfaceDark,
|
||||
GlassColors.GlassAccentBlue.copy(alpha = 0.15f),
|
||||
GlassColors.GlassSurfaceDark
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
GlassColors.GlassSurfaceLight,
|
||||
Color(0x20007ACC),
|
||||
GlassColors.GlassSurfaceLight
|
||||
)
|
||||
}
|
||||
|
||||
val shape = RoundedCornerShape(cornerRadius)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(shape)
|
||||
.background(
|
||||
brush = Brush.horizontalGradient(shimmerColors)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = if (isDark) GlassColors.GlassBorderDark else GlassColors.GlassBorderLight,
|
||||
shape = shape
|
||||
)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Floating glass button with elevation
|
||||
*/
|
||||
@Composable
|
||||
fun GlassButton(
|
||||
modifier: Modifier = Modifier,
|
||||
cornerRadius: Dp = 16.dp,
|
||||
content: @Composable BoxScope.() -> Unit
|
||||
) {
|
||||
val isDark = isSystemInDarkTheme()
|
||||
|
||||
val buttonSurface = if (isDark) {
|
||||
Color(0xDD2C2C2E)
|
||||
} else {
|
||||
Color(0xEEFFFFFF)
|
||||
}
|
||||
|
||||
val shape = RoundedCornerShape(cornerRadius)
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(shape)
|
||||
.background(buttonSurface)
|
||||
.border(
|
||||
width = 1.5.dp,
|
||||
color = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),
|
||||
shape = shape
|
||||
)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
package uk.silverlabs.silverdroid.ui.launcher
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import uk.silverlabs.silverdroid.data.model.PwaApp
|
||||
import uk.silverlabs.silverdroid.ui.components.GlassBackground
|
||||
import uk.silverlabs.silverdroid.ui.components.GlassCard
|
||||
import uk.silverlabs.silverdroid.ui.components.FrostedGlassPanel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LauncherScreen(
|
||||
apps: List<PwaApp>,
|
||||
onAppClick: (PwaApp) -> Unit,
|
||||
onAddAppClick: () -> Unit,
|
||||
onSettingsClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
// Glassmorphism gradient background
|
||||
GlassBackground()
|
||||
|
||||
Scaffold(
|
||||
containerColor = androidx.compose.ui.graphics.Color.Transparent,
|
||||
topBar = {
|
||||
LauncherTopBar(
|
||||
onSettingsClick = onSettingsClick
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
onClick = onAddAppClick,
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f),
|
||||
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Add App")
|
||||
}
|
||||
}
|
||||
) { paddingValues ->
|
||||
if (apps.isEmpty()) {
|
||||
EmptyState(
|
||||
onAddAppClick = onAddAppClick,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
)
|
||||
} else {
|
||||
AppGrid(
|
||||
apps = apps,
|
||||
onAppClick = onAppClick,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun LauncherTopBar(
|
||||
onSettingsClick: () -> Unit
|
||||
) {
|
||||
FrostedGlassPanel(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
cornerRadius = 24.dp
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
"SilverDROID",
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = onSettingsClick) {
|
||||
Icon(Icons.Default.Settings, contentDescription = "Settings")
|
||||
}
|
||||
},
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = androidx.compose.ui.graphics.Color.Transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppGrid(
|
||||
apps: List<PwaApp>,
|
||||
onAppClick: (PwaApp) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Adaptive(minSize = 120.dp),
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
modifier = modifier
|
||||
) {
|
||||
items(apps, key = { it.id }) { app ->
|
||||
AppCard(
|
||||
app = app,
|
||||
onClick = { onAppClick(app) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AppCard(
|
||||
app: PwaApp,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
GlassCard(
|
||||
modifier = modifier
|
||||
.aspectRatio(1f)
|
||||
.clickable(onClick = onClick),
|
||||
cornerRadius = 20.dp
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
// App icon placeholder
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.clip(CircleShape),
|
||||
color = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.8f)
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
Text(
|
||||
text = app.name.take(2).uppercase(),
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
||||
// App name
|
||||
Text(
|
||||
text = app.name,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmptyState(
|
||||
onAddAppClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.padding(32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
FrostedGlassPanel(
|
||||
modifier = Modifier.padding(24.dp),
|
||||
cornerRadius = 28.dp
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(
|
||||
text = "No Apps Yet",
|
||||
style = MaterialTheme.typography.headlineMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
Text(
|
||||
text = "Add your first PWA or WASM app to get started",
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Button(
|
||||
onClick = onAddAppClick,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.9f)
|
||||
)
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = null)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text("Add App")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package uk.silverlabs.silverdroid.ui.launcher
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import uk.silverlabs.silverdroid.data.PwaDatabase
|
||||
import uk.silverlabs.silverdroid.data.model.PwaApp
|
||||
import uk.silverlabs.silverdroid.data.repository.PwaRepository
|
||||
|
||||
class LauncherViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private val repository: PwaRepository
|
||||
|
||||
private val _apps = MutableStateFlow<List<PwaApp>>(emptyList())
|
||||
val apps: StateFlow<List<PwaApp>> = _apps.asStateFlow()
|
||||
|
||||
private val _isLoading = MutableStateFlow(false)
|
||||
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
|
||||
|
||||
init {
|
||||
val database = PwaDatabase.getInstance(application)
|
||||
repository = PwaRepository(database.pwaAppDao())
|
||||
|
||||
loadApps()
|
||||
}
|
||||
|
||||
private fun loadApps() {
|
||||
viewModelScope.launch {
|
||||
repository.allApps.collect { appList ->
|
||||
_apps.value = appList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addApp(url: String, name: String? = null) {
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
try {
|
||||
repository.installPwaFromUrl(url, name)
|
||||
} finally {
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteApp(app: PwaApp) {
|
||||
viewModelScope.launch {
|
||||
repository.deleteApp(app)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateLastAccessed(appId: Long) {
|
||||
viewModelScope.launch {
|
||||
repository.updateLastAccessed(appId)
|
||||
}
|
||||
}
|
||||
|
||||
fun addSampleApps() {
|
||||
viewModelScope.launch {
|
||||
// Add some sample PWAs for demonstration
|
||||
val samples = listOf(
|
||||
PwaApp(
|
||||
name = "Twitter",
|
||||
url = "https://mobile.twitter.com",
|
||||
description = "Social media platform"
|
||||
),
|
||||
PwaApp(
|
||||
name = "Spotify",
|
||||
url = "https://open.spotify.com",
|
||||
description = "Music streaming"
|
||||
),
|
||||
PwaApp(
|
||||
name = "YouTube",
|
||||
url = "https://m.youtube.com",
|
||||
description = "Video platform"
|
||||
),
|
||||
PwaApp(
|
||||
name = "WebAssembly Demo",
|
||||
url = "https://webassembly.org/demo/",
|
||||
description = "WASM showcase",
|
||||
isWasmApp = true
|
||||
)
|
||||
)
|
||||
|
||||
samples.forEach { app ->
|
||||
repository.insertApp(app)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package uk.silverlabs.silverdroid.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
// Glassmorphism Colors
|
||||
object GlassColors {
|
||||
// Light theme glass
|
||||
val GlassSurfaceLight = Color(0xE6FFFFFF)
|
||||
val GlassTintLight = Color(0x40FFFFFF)
|
||||
val GlassBorderLight = Color(0x60FFFFFF)
|
||||
|
||||
// Dark theme glass
|
||||
val GlassSurfaceDark = Color(0xCC1C1B1F)
|
||||
val GlassTintDark = Color(0x40000000)
|
||||
val GlassBorderDark = Color(0x60FFFFFF)
|
||||
|
||||
// Accent colors for glass effects
|
||||
val GlassAccentBlue = Color(0x4000B8FF)
|
||||
val GlassAccentPurple = Color(0x40BB86FC)
|
||||
}
|
||||
|
||||
// Material Design 3 Light Theme
|
||||
val md_theme_light_primary = Color(0xFF0061A4)
|
||||
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_primaryContainer = Color(0xFFD1E4FF)
|
||||
val md_theme_light_onPrimaryContainer = Color(0xFF001D36)
|
||||
|
||||
val md_theme_light_secondary = Color(0xFF535E70)
|
||||
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_secondaryContainer = Color(0xFFD7E2F7)
|
||||
val md_theme_light_onSecondaryContainer = Color(0xFF10192B)
|
||||
|
||||
val md_theme_light_tertiary = Color(0xFF6C5677)
|
||||
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
|
||||
val md_theme_light_tertiaryContainer = Color(0xFFF5D9FF)
|
||||
val md_theme_light_onTertiaryContainer = Color(0xFF261431)
|
||||
|
||||
val md_theme_light_error = Color(0xFFBA1A1A)
|
||||
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
|
||||
val md_theme_light_onError = Color(0xFFFFFFFF)
|
||||
val md_theme_light_onErrorContainer = Color(0xFF410002)
|
||||
|
||||
val md_theme_light_background = Color(0xFFFDFCFF)
|
||||
val md_theme_light_onBackground = Color(0xFF1A1C1E)
|
||||
val md_theme_light_surface = Color(0xFFFDFCFF)
|
||||
val md_theme_light_onSurface = Color(0xFF1A1C1E)
|
||||
val md_theme_light_surfaceVariant = Color(0xFFDEE3EB)
|
||||
val md_theme_light_onSurfaceVariant = Color(0xFF42474E)
|
||||
val md_theme_light_outline = Color(0xFF72777F)
|
||||
|
||||
// Material Design 3 Dark Theme
|
||||
val md_theme_dark_primary = Color(0xFF9ECAFF)
|
||||
val md_theme_dark_onPrimary = Color(0xFF003258)
|
||||
val md_theme_dark_primaryContainer = Color(0xFF00497D)
|
||||
val md_theme_dark_onPrimaryContainer = Color(0xFFD1E4FF)
|
||||
|
||||
val md_theme_dark_secondary = Color(0xFFBBC6DB)
|
||||
val md_theme_dark_onSecondary = Color(0xFF253140)
|
||||
val md_theme_dark_secondaryContainer = Color(0xFF3C4758)
|
||||
val md_theme_dark_onSecondaryContainer = Color(0xFFD7E2F7)
|
||||
|
||||
val md_theme_dark_tertiary = Color(0xFFD8BDE4)
|
||||
val md_theme_dark_onTertiary = Color(0xFF3C2947)
|
||||
val md_theme_dark_tertiaryContainer = Color(0xFF543F5F)
|
||||
val md_theme_dark_onTertiaryContainer = Color(0xFFF5D9FF)
|
||||
|
||||
val md_theme_dark_error = Color(0xFFFFB4AB)
|
||||
val md_theme_dark_errorContainer = Color(0xFF93000A)
|
||||
val md_theme_dark_onError = Color(0xFF690005)
|
||||
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
|
||||
|
||||
val md_theme_dark_background = Color(0xFF1A1C1E)
|
||||
val md_theme_dark_onBackground = Color(0xFFE2E2E6)
|
||||
val md_theme_dark_surface = Color(0xFF1A1C1E)
|
||||
val md_theme_dark_onSurface = Color(0xFFE2E2E6)
|
||||
val md_theme_dark_surfaceVariant = Color(0xFF42474E)
|
||||
val md_theme_dark_onSurfaceVariant = Color(0xFFC2C7CF)
|
||||
val md_theme_dark_outline = Color(0xFF8C9199)
|
||||
101
app/src/main/kotlin/uk/silverlabs/silverdroid/ui/theme/Theme.kt
Normal file
101
app/src/main/kotlin/uk/silverlabs/silverdroid/ui/theme/Theme.kt
Normal file
@@ -0,0 +1,101 @@
|
||||
package uk.silverlabs.silverdroid.ui.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = md_theme_light_primary,
|
||||
onPrimary = md_theme_light_onPrimary,
|
||||
primaryContainer = md_theme_light_primaryContainer,
|
||||
onPrimaryContainer = md_theme_light_onPrimaryContainer,
|
||||
secondary = md_theme_light_secondary,
|
||||
onSecondary = md_theme_light_onSecondary,
|
||||
secondaryContainer = md_theme_light_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_light_onSecondaryContainer,
|
||||
tertiary = md_theme_light_tertiary,
|
||||
onTertiary = md_theme_light_onTertiary,
|
||||
tertiaryContainer = md_theme_light_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_light_onTertiaryContainer,
|
||||
error = md_theme_light_error,
|
||||
errorContainer = md_theme_light_errorContainer,
|
||||
onError = md_theme_light_onError,
|
||||
onErrorContainer = md_theme_light_onErrorContainer,
|
||||
background = md_theme_light_background,
|
||||
onBackground = md_theme_light_onBackground,
|
||||
surface = md_theme_light_surface,
|
||||
onSurface = md_theme_light_onSurface,
|
||||
surfaceVariant = md_theme_light_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_light_onSurfaceVariant,
|
||||
outline = md_theme_light_outline,
|
||||
)
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = md_theme_dark_primary,
|
||||
onPrimary = md_theme_dark_onPrimary,
|
||||
primaryContainer = md_theme_dark_primaryContainer,
|
||||
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
|
||||
secondary = md_theme_dark_secondary,
|
||||
onSecondary = md_theme_dark_onSecondary,
|
||||
secondaryContainer = md_theme_dark_secondaryContainer,
|
||||
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
|
||||
tertiary = md_theme_dark_tertiary,
|
||||
onTertiary = md_theme_dark_onTertiary,
|
||||
tertiaryContainer = md_theme_dark_tertiaryContainer,
|
||||
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
|
||||
error = md_theme_dark_error,
|
||||
errorContainer = md_theme_dark_errorContainer,
|
||||
onError = md_theme_dark_onError,
|
||||
onErrorContainer = md_theme_dark_onErrorContainer,
|
||||
background = md_theme_dark_background,
|
||||
onBackground = md_theme_dark_onBackground,
|
||||
surface = md_theme_dark_surface,
|
||||
onSurface = md_theme_dark_onSurface,
|
||||
surfaceVariant = md_theme_dark_surfaceVariant,
|
||||
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
|
||||
outline = md_theme_dark_outline,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun SilverDROIDTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as android.app.Activity).window
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
WindowCompat.getInsetsController(window, view).apply {
|
||||
isAppearanceLightStatusBars = !darkTheme
|
||||
isAppearanceLightNavigationBars = !darkTheme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
115
app/src/main/kotlin/uk/silverlabs/silverdroid/ui/theme/Type.kt
Normal file
115
app/src/main/kotlin/uk/silverlabs/silverdroid/ui/theme/Type.kt
Normal file
@@ -0,0 +1,115 @@
|
||||
package uk.silverlabs.silverdroid.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
val Typography = Typography(
|
||||
displayLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 57.sp,
|
||||
lineHeight = 64.sp,
|
||||
letterSpacing = (-0.25).sp,
|
||||
),
|
||||
displayMedium = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 45.sp,
|
||||
lineHeight = 52.sp,
|
||||
letterSpacing = 0.sp,
|
||||
),
|
||||
displaySmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 36.sp,
|
||||
lineHeight = 44.sp,
|
||||
letterSpacing = 0.sp,
|
||||
),
|
||||
headlineLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 32.sp,
|
||||
lineHeight = 40.sp,
|
||||
letterSpacing = 0.sp,
|
||||
),
|
||||
headlineMedium = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 28.sp,
|
||||
lineHeight = 36.sp,
|
||||
letterSpacing = 0.sp,
|
||||
),
|
||||
headlineSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 24.sp,
|
||||
lineHeight = 32.sp,
|
||||
letterSpacing = 0.sp,
|
||||
),
|
||||
titleLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 22.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp,
|
||||
),
|
||||
titleMedium = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.15.sp,
|
||||
),
|
||||
titleSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.1.sp,
|
||||
),
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp,
|
||||
),
|
||||
bodyMedium = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.25.sp,
|
||||
),
|
||||
bodySmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.4.sp,
|
||||
),
|
||||
labelLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 20.sp,
|
||||
letterSpacing = 0.1.sp,
|
||||
),
|
||||
labelMedium = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 12.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp,
|
||||
),
|
||||
labelSmall = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = 11.sp,
|
||||
lineHeight = 16.sp,
|
||||
letterSpacing = 0.5.sp,
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,204 @@
|
||||
package uk.silverlabs.silverdroid.ui.webview
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Bitmap
|
||||
import android.webkit.*
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun WasmWebView(
|
||||
url: String,
|
||||
appName: String,
|
||||
onBackPressed: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
var webView by remember { mutableStateOf<WebView?>(null) }
|
||||
var isLoading by remember { mutableStateOf(true) }
|
||||
var loadProgress by remember { mutableIntStateOf(0) }
|
||||
var pageTitle by remember { mutableStateOf(appName) }
|
||||
var canGoBack by remember { mutableStateOf(false) }
|
||||
|
||||
BackHandler(enabled = canGoBack) {
|
||||
webView?.goBack()
|
||||
}
|
||||
|
||||
Column(modifier = modifier.fillMaxSize()) {
|
||||
// Top bar with navigation
|
||||
Surface(
|
||||
tonalElevation = 3.dp,
|
||||
shadowElevation = 3.dp
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Column {
|
||||
Text(
|
||||
text = pageTitle,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
if (isLoading) {
|
||||
LinearProgressIndicator(
|
||||
progress = { loadProgress / 100f },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(2.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
if (canGoBack) {
|
||||
webView?.goBack()
|
||||
} else {
|
||||
onBackPressed()
|
||||
}
|
||||
}) {
|
||||
Icon(Icons.AutoMirrored.Filled.ArrowBack, "Back")
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { webView?.reload() }) {
|
||||
Icon(Icons.Default.Refresh, "Refresh")
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// WebView
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
WebView(context).apply {
|
||||
webView = this
|
||||
|
||||
// Enable JavaScript
|
||||
settings.javaScriptEnabled = true
|
||||
|
||||
// Enable WASM support
|
||||
settings.allowFileAccess = true
|
||||
settings.allowContentAccess = true
|
||||
|
||||
// Enable DOM storage (required for PWAs)
|
||||
settings.domStorageEnabled = true
|
||||
|
||||
// Enable database storage
|
||||
settings.databaseEnabled = true
|
||||
|
||||
// Enable caching for offline support
|
||||
settings.cacheMode = WebSettings.LOAD_DEFAULT
|
||||
settings.setAppCacheEnabled(true)
|
||||
|
||||
// Enable mixed content (for development)
|
||||
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
||||
|
||||
// Performance optimizations
|
||||
settings.setRenderPriority(WebSettings.RenderPriority.HIGH)
|
||||
settings.cacheMode = WebSettings.LOAD_DEFAULT
|
||||
|
||||
// Enable zooming
|
||||
settings.setSupportZoom(true)
|
||||
settings.builtInZoomControls = true
|
||||
settings.displayZoomControls = false
|
||||
|
||||
// User agent (modern)
|
||||
settings.userAgentString = settings.userAgentString +
|
||||
" SilverDROID/1.0 (PWA/WASM Launcher)"
|
||||
|
||||
// Enable wide viewport
|
||||
settings.useWideViewPort = true
|
||||
settings.loadWithOverviewMode = true
|
||||
|
||||
// WebView client
|
||||
webViewClient = object : WebViewClient() {
|
||||
override fun onPageStarted(
|
||||
view: WebView?,
|
||||
url: String?,
|
||||
favicon: Bitmap?
|
||||
) {
|
||||
isLoading = true
|
||||
loadProgress = 0
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
isLoading = false
|
||||
loadProgress = 100
|
||||
canGoBack = view?.canGoBack() ?: false
|
||||
pageTitle = view?.title ?: appName
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest?
|
||||
): Boolean {
|
||||
// Allow all navigation within WebView
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onReceivedError(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest?,
|
||||
error: WebResourceError?
|
||||
) {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
// Chrome client for progress
|
||||
webChromeClient = object : WebChromeClient() {
|
||||
override fun onProgressChanged(view: WebView?, newProgress: Int) {
|
||||
loadProgress = newProgress
|
||||
if (newProgress == 100) {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onReceivedTitle(view: WebView?, title: String?) {
|
||||
pageTitle = title ?: appName
|
||||
}
|
||||
|
||||
override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean {
|
||||
// Log console messages for debugging
|
||||
consoleMessage?.let {
|
||||
android.util.Log.d(
|
||||
"WebView",
|
||||
"${it.message()} -- From line ${it.lineNumber()} of ${it.sourceId()}"
|
||||
)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Load the URL
|
||||
loadUrl(url)
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
canGoBack = view.canGoBack()
|
||||
},
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
|
||||
// Loading indicator
|
||||
if (isLoading && loadProgress < 20) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package uk.silverlabs.silverdroid.ui.webview
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.webkit.WebView
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.ui.Modifier
|
||||
import uk.silverlabs.silverdroid.ui.theme.SilverDROIDTheme
|
||||
|
||||
class WebViewActivity : ComponentActivity() {
|
||||
companion object {
|
||||
const val EXTRA_APP_ID = "app_id"
|
||||
const val EXTRA_APP_URL = "app_url"
|
||||
const val EXTRA_APP_NAME = "app_name"
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
|
||||
val appUrl = intent.getStringExtra(EXTRA_APP_URL) ?: ""
|
||||
val appName = intent.getStringExtra(EXTRA_APP_NAME) ?: "App"
|
||||
|
||||
// Enable WebView debugging in debug builds
|
||||
WebView.setWebContentsDebuggingEnabled(true)
|
||||
|
||||
setContent {
|
||||
SilverDROIDTheme {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
WasmWebView(
|
||||
url = appUrl,
|
||||
appName = appName,
|
||||
onBackPressed = { finish() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
app/src/main/res/values/colors.xml
Normal file
74
app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Glassmorphism Color Palette -->
|
||||
|
||||
<!-- Light Theme Glass -->
|
||||
<color name="glass_surface_light">#E6FFFFFF</color>
|
||||
<color name="glass_tint_light">#40FFFFFF</color>
|
||||
<color name="glass_border_light">#60FFFFFF</color>
|
||||
|
||||
<!-- Dark Theme Glass -->
|
||||
<color name="glass_surface_dark">#CC1C1B1F</color>
|
||||
<color name="glass_tint_dark">#40000000</color>
|
||||
<color name="glass_border_dark">#60FFFFFF</color>
|
||||
|
||||
<!-- Material Design 3 Seeds -->
|
||||
<color name="md_theme_light_primary">#0061A4</color>
|
||||
<color name="md_theme_light_onPrimary">#FFFFFF</color>
|
||||
<color name="md_theme_light_primaryContainer">#D1E4FF</color>
|
||||
<color name="md_theme_light_onPrimaryContainer">#001D36</color>
|
||||
|
||||
<color name="md_theme_light_secondary">#535E70</color>
|
||||
<color name="md_theme_light_onSecondary">#FFFFFF</color>
|
||||
<color name="md_theme_light_secondaryContainer">#D7E2F7</color>
|
||||
<color name="md_theme_light_onSecondaryContainer">#10192B</color>
|
||||
|
||||
<color name="md_theme_light_tertiary">#6C5677</color>
|
||||
<color name="md_theme_light_onTertiary">#FFFFFF</color>
|
||||
<color name="md_theme_light_tertiaryContainer">#F5D9FF</color>
|
||||
<color name="md_theme_light_onTertiaryContainer">#261431</color>
|
||||
|
||||
<color name="md_theme_light_error">#BA1A1A</color>
|
||||
<color name="md_theme_light_errorContainer">#FFDAD6</color>
|
||||
<color name="md_theme_light_onError">#FFFFFF</color>
|
||||
<color name="md_theme_light_onErrorContainer">#410002</color>
|
||||
|
||||
<color name="md_theme_light_background">#FDFCFF</color>
|
||||
<color name="md_theme_light_onBackground">#1A1C1E</color>
|
||||
<color name="md_theme_light_surface">#FDFCFF</color>
|
||||
<color name="md_theme_light_onSurface">#1A1C1E</color>
|
||||
|
||||
<color name="md_theme_light_surfaceVariant">#DEE3EB</color>
|
||||
<color name="md_theme_light_onSurfaceVariant">#42474E</color>
|
||||
<color name="md_theme_light_outline">#72777F</color>
|
||||
|
||||
<!-- Dark Theme -->
|
||||
<color name="md_theme_dark_primary">#9ECAFF</color>
|
||||
<color name="md_theme_dark_onPrimary">#003258</color>
|
||||
<color name="md_theme_dark_primaryContainer">#00497D</color>
|
||||
<color name="md_theme_dark_onPrimaryContainer">#D1E4FF</color>
|
||||
|
||||
<color name="md_theme_dark_secondary">#BBC6DB</color>
|
||||
<color name="md_theme_dark_onSecondary">#253140</color>
|
||||
<color name="md_theme_dark_secondaryContainer">#3C4758</color>
|
||||
<color name="md_theme_dark_onSecondaryContainer">#D7E2F7</color>
|
||||
|
||||
<color name="md_theme_dark_tertiary">#D8BDE4</color>
|
||||
<color name="md_theme_dark_onTertiary">#3C2947</color>
|
||||
<color name="md_theme_dark_tertiaryContainer">#543F5F</color>
|
||||
<color name="md_theme_dark_onTertiaryContainer">#F5D9FF</color>
|
||||
|
||||
<color name="md_theme_dark_error">#FFB4AB</color>
|
||||
<color name="md_theme_dark_errorContainer">#93000A</color>
|
||||
<color name="md_theme_dark_onError">#690005</color>
|
||||
<color name="md_theme_dark_onErrorContainer">#FFDAD6</color>
|
||||
|
||||
<color name="md_theme_dark_background">#1A1C1E</color>
|
||||
<color name="md_theme_dark_onBackground">#E2E2E6</color>
|
||||
<color name="md_theme_dark_surface">#1A1C1E</color>
|
||||
<color name="md_theme_dark_onSurface">#E2E2E6</color>
|
||||
|
||||
<color name="md_theme_dark_surfaceVariant">#42474E</color>
|
||||
<color name="md_theme_dark_onSurfaceVariant">#C2C7CF</color>
|
||||
<color name="md_theme_dark_outline">#8C9199</color>
|
||||
</resources>
|
||||
17
app/src/main/res/values/strings.xml
Normal file
17
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Dark Side Admin</string>
|
||||
<string name="launcher_title">Your Apps</string>
|
||||
<string name="add_pwa">Add App</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="search_apps">Search apps…</string>
|
||||
<string name="no_apps">No apps installed yet</string>
|
||||
<string name="add_first_app">Add your first PWA or WASM app</string>
|
||||
<string name="enter_url">Enter app URL</string>
|
||||
<string name="install">Install</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="uninstall">Uninstall</string>
|
||||
<string name="open">Open</string>
|
||||
<string name="loading">Loading…</string>
|
||||
<string name="error_loading">Failed to load app</string>
|
||||
</resources>
|
||||
14
app/src/main/res/values/themes.xml
Normal file
14
app/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.SilverDROID" parent="android:Theme.Material.Light.NoActionBar">
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:windowLightStatusBar">true</item>
|
||||
<item name="android:windowLightNavigationBar">true</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.SilverDROID.Fullscreen" parent="Theme.SilverDROID">
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
</style>
|
||||
</resources>
|
||||
5
app/src/main/res/xml/backup_rules.xml
Normal file
5
app/src/main/res/xml/backup_rules.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="database" path="pwa_cache.db"/>
|
||||
</full-backup-content>
|
||||
7
app/src/main/res/xml/data_extraction_rules.xml
Normal file
7
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="database" path="pwa_cache.db"/>
|
||||
</cloud-backup>
|
||||
</data-extraction-rules>
|
||||
7
build.gradle.kts
Normal file
7
build.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.7.3" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||
id("org.jetbrains.kotlin.plugin.compose") version "2.1.0" apply false
|
||||
id("com.google.devtools.ksp") version "2.1.0-1.0.29" apply false
|
||||
}
|
||||
13
gradle.properties
Normal file
13
gradle.properties
Normal file
@@ -0,0 +1,13 @@
|
||||
# Project-wide Gradle settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
org.gradle.parallel=true
|
||||
org.gradle.caching=true
|
||||
org.gradle.configuration-cache=true
|
||||
|
||||
# Android settings
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=false
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
# Kotlin
|
||||
kotlin.code.style=official
|
||||
BIN
gradle.zip
Normal file
BIN
gradle.zip
Normal file
Binary file not shown.
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
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.9-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
80
gradlew
vendored
Normal file
80
gradlew
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
#!/bin/sh
|
||||
|
||||
##############################################################################
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
##############################################################################
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
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"
|
||||
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."
|
||||
fi
|
||||
|
||||
# Determine the project base directory
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# 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 "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=${0##*/}
|
||||
|
||||
# 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
|
||||
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || 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
|
||||
|
||||
# Collect all arguments for the java command; same as "$@".
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
67
gradlew.bat
vendored
Normal file
67
gradlew.bat
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
@rem Gradle startup script for Windows
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@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 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.
|
||||
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% 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
|
||||
161
push-to-gitlab.ps1
Normal file
161
push-to-gitlab.ps1
Normal file
@@ -0,0 +1,161 @@
|
||||
# SilverDROID - Push to GitLab Script (PowerShell)
|
||||
# Automates the process of pushing the project to GitLab CE
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$GITLAB_URL = "https://gitlab.silverlabs.uk"
|
||||
$GITLAB_TOKEN = "glpat-wqUcD7mg53F1mgM-N-PdiW86MQp1OjEH.01.0w074ox93"
|
||||
$PROJECT_NAME = "silverdroid"
|
||||
$NAMESPACE = "SilverLABS"
|
||||
$PROJECT_PATH = "$NAMESPACE/$PROJECT_NAME"
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================" -ForegroundColor Cyan
|
||||
Write-Host " SilverDROID - GitLab Push Script" -ForegroundColor Cyan
|
||||
Write-Host "================================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Check if git is initialized
|
||||
if (-not (Test-Path .git)) {
|
||||
Write-Host "📦 Initializing Git repository..." -ForegroundColor Yellow
|
||||
git init
|
||||
Write-Host "✅ Git initialized" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "✅ Git repository already initialized" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Check if GitLab remote exists
|
||||
$remotes = git remote
|
||||
if ($remotes -contains "origin") {
|
||||
Write-Host "✅ Remote 'origin' already configured" -ForegroundColor Green
|
||||
$currentUrl = git remote get-url origin
|
||||
Write-Host " Current URL: $currentUrl" -ForegroundColor Gray
|
||||
|
||||
$updateRemote = Read-Host "Do you want to update the remote URL? (y/N)"
|
||||
if ($updateRemote -eq "y" -or $updateRemote -eq "Y") {
|
||||
git remote set-url origin "https://gitlab.silverlabs.uk/$PROJECT_PATH.git"
|
||||
Write-Host "✅ Remote URL updated" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "🔗 Adding GitLab remote..." -ForegroundColor Yellow
|
||||
git remote add origin "https://gitlab.silverlabs.uk/$PROJECT_PATH.git"
|
||||
Write-Host "✅ Remote added" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Check for uncommitted changes
|
||||
$status = git status -s
|
||||
if ($status) {
|
||||
Write-Host ""
|
||||
Write-Host "📝 Uncommitted changes detected:" -ForegroundColor Yellow
|
||||
git status -s
|
||||
Write-Host ""
|
||||
$commitChanges = Read-Host "Do you want to commit these changes? (Y/n)"
|
||||
|
||||
if ($commitChanges -ne "n" -and $commitChanges -ne "N") {
|
||||
$commitMsg = Read-Host "Enter commit message (or press Enter for default)"
|
||||
if ([string]::IsNullOrWhiteSpace($commitMsg)) {
|
||||
$commitMsg = "SilverDROID - Dark Side Admin build configuration"
|
||||
}
|
||||
|
||||
Write-Host "📦 Adding files..." -ForegroundColor Yellow
|
||||
git add .
|
||||
|
||||
Write-Host "💾 Creating commit..." -ForegroundColor Yellow
|
||||
git commit -m $commitMsg
|
||||
Write-Host "✅ Changes committed" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host "✅ No uncommitted changes" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Check current branch
|
||||
$currentBranch = git branch --show-current
|
||||
if ([string]::IsNullOrWhiteSpace($currentBranch)) {
|
||||
Write-Host "🌿 Creating main branch..." -ForegroundColor Yellow
|
||||
git checkout -b main
|
||||
$currentBranch = "main"
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Current branch: $currentBranch" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Push to GitLab
|
||||
$pushConfirm = Read-Host "Ready to push to GitLab? (Y/n)"
|
||||
if ($pushConfirm -ne "n" -and $pushConfirm -ne "N") {
|
||||
Write-Host ""
|
||||
Write-Host "🚀 Pushing to GitLab..." -ForegroundColor Yellow
|
||||
|
||||
try {
|
||||
git push -u origin $currentBranch
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host " ✅ Successfully pushed to GitLab!" -ForegroundColor Green
|
||||
Write-Host "================================================" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
Write-Host "📍 Project URL:" -ForegroundColor Cyan
|
||||
Write-Host " $GITLAB_URL/$PROJECT_PATH" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "🔧 Pipeline URL:" -ForegroundColor Cyan
|
||||
Write-Host " $GITLAB_URL/$PROJECT_PATH/-/pipelines" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "📦 CI/CD Jobs:" -ForegroundColor Cyan
|
||||
Write-Host " $GITLAB_URL/$PROJECT_PATH/-/jobs" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Next steps:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Visit the project URL above"
|
||||
Write-Host " 2. Check the pipeline status"
|
||||
Write-Host " 3. Wait for build to complete (~5-8 minutes)"
|
||||
Write-Host " 4. Download APK from artifacts"
|
||||
Write-Host ""
|
||||
|
||||
} catch {
|
||||
Write-Host "❌ Push failed: $_" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Try authenticating with:" -ForegroundColor Yellow
|
||||
Write-Host " git config --global credential.helper wincred"
|
||||
Write-Host " git push -u origin $currentBranch"
|
||||
exit 1
|
||||
}
|
||||
} else {
|
||||
Write-Host "❌ Push cancelled" -ForegroundColor Red
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Optional: Create project description
|
||||
$updateDesc = Read-Host "Update project description on GitLab? (y/N)"
|
||||
if ($updateDesc -eq "y" -or $updateDesc -eq "Y") {
|
||||
Write-Host "📝 Updating project description..." -ForegroundColor Yellow
|
||||
|
||||
try {
|
||||
$headers = @{
|
||||
"PRIVATE-TOKEN" = $GITLAB_TOKEN
|
||||
"Content-Type" = "application/json"
|
||||
}
|
||||
|
||||
$searchUrl = "$GITLAB_URL/api/v4/projects?search=$PROJECT_NAME"
|
||||
$projects = Invoke-RestMethod -Uri $searchUrl -Headers $headers -Method Get
|
||||
|
||||
if ($projects.Count -gt 0) {
|
||||
$projectId = $projects[0].id
|
||||
$updateUrl = "$GITLAB_URL/api/v4/projects/$projectId"
|
||||
|
||||
$body = @{
|
||||
description = "SilverDROID - Android PWA/WASM Launcher for Dark Side Admin. Glassmorphism UI with direct loading of admin.dark.side"
|
||||
topics = @("android", "pwa", "wasm", "glassmorphism", "launcher")
|
||||
} | ConvertTo-Json
|
||||
|
||||
Invoke-RestMethod -Uri $updateUrl -Headers $headers -Method Put -Body $body | Out-Null
|
||||
Write-Host "✅ Project description updated" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "⚠️ Could not find project" -ForegroundColor Yellow
|
||||
}
|
||||
} catch {
|
||||
Write-Host "⚠️ Could not update description: $_" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "🎉 All done! Your pipeline should start automatically." -ForegroundColor Green
|
||||
Write-Host ""
|
||||
145
push-to-gitlab.sh
Normal file
145
push-to-gitlab.sh
Normal file
@@ -0,0 +1,145 @@
|
||||
#!/bin/bash
|
||||
|
||||
# SilverDROID - Push to GitLab Script
|
||||
# Automates the process of pushing the project to GitLab CE
|
||||
|
||||
set -e
|
||||
|
||||
GITLAB_URL="https://gitlab.silverlabs.uk"
|
||||
GITLAB_TOKEN="glpat-wqUcD7mg53F1mgM-N-PdiW86MQp1OjEH.01.0w074ox93"
|
||||
PROJECT_NAME="silverdroid"
|
||||
NAMESPACE="SilverLABS"
|
||||
PROJECT_PATH="${NAMESPACE}/${PROJECT_NAME}"
|
||||
|
||||
echo "================================================"
|
||||
echo " SilverDROID - GitLab Push Script"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
# Check if git is initialized
|
||||
if [ ! -d .git ]; then
|
||||
echo "📦 Initializing Git repository..."
|
||||
git init
|
||||
echo "✅ Git initialized"
|
||||
else
|
||||
echo "✅ Git repository already initialized"
|
||||
fi
|
||||
|
||||
# Check if GitLab remote exists
|
||||
if git remote | grep -q "origin"; then
|
||||
echo "✅ Remote 'origin' already configured"
|
||||
CURRENT_URL=$(git remote get-url origin)
|
||||
echo " Current URL: $CURRENT_URL"
|
||||
|
||||
read -p "Do you want to update the remote URL? (y/N): " UPDATE_REMOTE
|
||||
if [[ $UPDATE_REMOTE =~ ^[Yy]$ ]]; then
|
||||
git remote set-url origin "https://gitlab.silverlabs.uk/${PROJECT_PATH}.git"
|
||||
echo "✅ Remote URL updated"
|
||||
fi
|
||||
else
|
||||
echo "🔗 Adding GitLab remote..."
|
||||
git remote add origin "https://gitlab.silverlabs.uk/${PROJECT_PATH}.git"
|
||||
echo "✅ Remote added"
|
||||
fi
|
||||
|
||||
# Check for uncommitted changes
|
||||
if [[ -n $(git status -s) ]]; then
|
||||
echo ""
|
||||
echo "📝 Uncommitted changes detected:"
|
||||
git status -s
|
||||
echo ""
|
||||
read -p "Do you want to commit these changes? (Y/n): " COMMIT_CHANGES
|
||||
|
||||
if [[ ! $COMMIT_CHANGES =~ ^[Nn]$ ]]; then
|
||||
read -p "Enter commit message (or press Enter for default): " COMMIT_MSG
|
||||
if [ -z "$COMMIT_MSG" ]; then
|
||||
COMMIT_MSG="SilverDROID - Dark Side Admin build configuration"
|
||||
fi
|
||||
|
||||
echo "📦 Adding files..."
|
||||
git add .
|
||||
|
||||
echo "💾 Creating commit..."
|
||||
git commit -m "$COMMIT_MSG"
|
||||
echo "✅ Changes committed"
|
||||
fi
|
||||
else
|
||||
echo "✅ No uncommitted changes"
|
||||
fi
|
||||
|
||||
# Check current branch
|
||||
CURRENT_BRANCH=$(git branch --show-current)
|
||||
if [ -z "$CURRENT_BRANCH" ]; then
|
||||
echo "🌿 Creating main branch..."
|
||||
git checkout -b main
|
||||
CURRENT_BRANCH="main"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Current branch: $CURRENT_BRANCH"
|
||||
echo ""
|
||||
|
||||
# Push to GitLab
|
||||
read -p "Ready to push to GitLab? (Y/n): " PUSH_CONFIRM
|
||||
if [[ ! $PUSH_CONFIRM =~ ^[Nn]$ ]]; then
|
||||
echo ""
|
||||
echo "🚀 Pushing to GitLab..."
|
||||
|
||||
# Set up credential helper for this push
|
||||
git config --local credential.helper store
|
||||
|
||||
# Push with token authentication
|
||||
git push -u origin $CURRENT_BRANCH
|
||||
|
||||
echo ""
|
||||
echo "================================================"
|
||||
echo " ✅ Successfully pushed to GitLab!"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
echo "📍 Project URL:"
|
||||
echo " ${GITLAB_URL}/${PROJECT_PATH}"
|
||||
echo ""
|
||||
echo "🔧 Pipeline URL:"
|
||||
echo " ${GITLAB_URL}/${PROJECT_PATH}/-/pipelines"
|
||||
echo ""
|
||||
echo "📦 CI/CD Jobs:"
|
||||
echo " ${GITLAB_URL}/${PROJECT_PATH}/-/jobs"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. Visit the project URL above"
|
||||
echo " 2. Check the pipeline status"
|
||||
echo " 3. Wait for build to complete (~5-8 minutes)"
|
||||
echo " 4. Download APK from artifacts"
|
||||
echo ""
|
||||
else
|
||||
echo "❌ Push cancelled"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Optional: Create project description
|
||||
read -p "Update project description on GitLab? (y/N): " UPDATE_DESC
|
||||
if [[ $UPDATE_DESC =~ ^[Yy]$ ]]; then
|
||||
echo "📝 Updating project description..."
|
||||
|
||||
PROJECT_ID=$(curl -s "${GITLAB_URL}/api/v4/projects?search=${PROJECT_NAME}" \
|
||||
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" | \
|
||||
jq -r ".[0].id")
|
||||
|
||||
if [ "$PROJECT_ID" != "null" ]; then
|
||||
curl -s -X PUT "${GITLAB_URL}/api/v4/projects/${PROJECT_ID}" \
|
||||
--header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" \
|
||||
--header "Content-Type: application/json" \
|
||||
--data '{
|
||||
"description": "SilverDROID - Android PWA/WASM Launcher for Dark Side Admin. Glassmorphism UI with direct loading of admin.dark.side",
|
||||
"topics": ["android", "pwa", "wasm", "glassmorphism", "launcher"]
|
||||
}' > /dev/null
|
||||
|
||||
echo "✅ Project description updated"
|
||||
else
|
||||
echo "⚠️ Could not find project ID"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🎉 All done! Your pipeline should start automatically."
|
||||
echo ""
|
||||
18
settings.gradle.kts
Normal file
18
settings.gradle.kts
Normal file
@@ -0,0 +1,18 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "SilverDROID"
|
||||
include(":app")
|
||||
Reference in New Issue
Block a user