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