diff --git a/.github/workflows/cross-cd.yml b/.github/workflows/cross-cd.yml
new file mode 100644
index 00000000..950bfca6
--- /dev/null
+++ b/.github/workflows/cross-cd.yml
@@ -0,0 +1,150 @@
+name: Cross-Platform — CD Release
+
+on:
+ push:
+ tags:
+ - "v*"
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "Version tag for this release (e.g. v1.0.0)"
+ required: true
+
+jobs:
+ # JOB 1 — Build Android release APK
+ build-android-apk:
+ name: Build Android APK
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Set up Java 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "17"
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.41.4"
+ channel: stable
+ cache: true
+
+ - name: Clean Flutter project
+ run: flutter clean
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Build Android release APK
+ run: flutter build apk --release --no-shrink
+
+ - name: Confirm APK was produced
+ run: |
+ test -f build/app/outputs/flutter-apk/app-release.apk || \
+ { echo "Release APK not found — build may have silently failed."; exit 1; }
+ echo "Android release APK produced successfully."
+
+ - name: Upload APK artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: android-release-apk
+ path: build/app/outputs/flutter-apk/app-release.apk
+ retention-days: 1
+
+ # JOB 2 — Build Windows executable (must run on windows-latest)
+ build-windows-exe:
+ name: Build Windows Executable
+ runs-on: windows-latest
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.41.4"
+ channel: stable
+ cache: true
+
+ - name: Enable Windows desktop support
+ run: flutter config --enable-windows-desktop
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Build Windows release
+ run: flutter build windows --release --no-pub
+
+ - name: Zip Windows release folder
+ run: |
+ Compress-Archive -Path "build\windows\x64\runner\Release\*" -DestinationPath "rythmify-windows.zip"
+
+ - name: Confirm zip was produced
+ run: |
+ if (-not (Test-Path "rythmify-windows.zip")) {
+ Write-Error "rythmify-windows.zip not found — build may have silently failed."
+ exit 1
+ }
+ Write-Host "Windows release zip produced successfully."
+
+ - name: Upload Windows artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: windows-release-zip
+ path: rythmify-windows.zip
+ retention-days: 1
+
+ # JOB 3 — Create GitHub Release with permanent download URLs
+ create-release:
+ name: Create GitHub Release
+ runs-on: ubuntu-latest
+ needs: [build-android-apk, build-windows-exe]
+
+ steps:
+ - name: Determine release version
+ id: version
+ run: |
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
+ echo "tag=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
+ else
+ echo "tag=${{ github.ref_name }}" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Download Android APK artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: android-release-apk
+ path: android-release-apk
+
+ - name: Download Windows zip artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: windows-release-zip
+ path: windows-release-zip
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ steps.version.outputs.tag }}
+ name: "Rythmify ${{ steps.version.outputs.tag }}"
+ body: |
+ ## Rythmify ${{ steps.version.outputs.tag }}
+
+ ### Downloads
+ - **Android APK** — Install on your Android device
+ - **Windows Desktop** — Extract the zip and run `rythmify.exe`
+
+ ### What's New
+ See the [changelog](https://github.com/Rythmify/cross/blob/main/CHANGELOG.md) for details.
+ files: |
+ android-release-apk/app-release.apk
+ windows-release-zip/rythmify-windows.zip
+ draft: false
+ prerelease: ${{ contains(steps.version.outputs.tag, 'beta') || contains(steps.version.outputs.tag, 'alpha') }}
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/cross-dev.yml b/.github/workflows/cross-dev.yml
new file mode 100644
index 00000000..c07293b5
--- /dev/null
+++ b/.github/workflows/cross-dev.yml
@@ -0,0 +1,125 @@
+name: Cross-Platform — Dev Pipeline
+
+on:
+ pull_request:
+ branches:
+ - Development
+
+jobs:
+ # JOB 1 — Validate repo structure
+ validate-structure:
+ name: Validate Repo Structure
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Check pubspec files exist
+ run: |
+ test -f pubspec.yaml || { echo "MISSING: pubspec.yaml"; exit 1; }
+ test -f pubspec.lock || { echo "MISSING: pubspec.lock"; exit 1; }
+ echo "pubspec files present."
+
+ - name: Check analysis options exist
+ run: |
+ test -f analysis_options.yaml || { echo "MISSING: analysis_options.yaml"; exit 1; }
+ echo "analysis_options.yaml present."
+
+ - name: Check main entry point exists
+ run: |
+ test -f lib/main.dart || { echo "MISSING: lib/main.dart"; exit 1; }
+ echo "lib/main.dart present."
+
+ - name: Check core structure exists
+ run: |
+ test -d lib/core || { echo "MISSING: lib/core/"; exit 1; }
+ test -d lib/features || { echo "MISSING: lib/features/"; exit 1; }
+ test -f lib/core/network/api_client.dart || { echo "MISSING: lib/core/network/api_client.dart"; exit 1; }
+ test -f lib/core/routing/app_router.dart || { echo "MISSING: lib/core/routing/app_router.dart"; exit 1; }
+ echo "Core structure present."
+
+ - name: Check no hardcoded base URLs in API client
+ run: |
+ echo "Scanning api_client.dart for hardcoded URLs..."
+ if grep -qE "http://[0-9]|https://[a-zA-Z0-9]" lib/core/network/api_client.dart; then
+ echo "WARNING: Possible hardcoded URL found in api_client.dart"
+ grep -nE "http://[0-9]|https://[a-zA-Z0-9]" lib/core/network/api_client.dart || true
+ else
+ echo "No hardcoded URLs detected in api_client.dart."
+ fi
+
+ # JOB 2 — Flutter analysis and format check
+ analyze:
+ name: Format Check & Static Analysis
+ runs-on: ubuntu-latest
+ needs: validate-structure
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.41.4"
+ channel: stable
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Verify dependency resolution
+ run: |
+ flutter pub deps > /dev/null && echo "Dependencies resolved cleanly." || \
+ { echo "Dependency resolution failed."; exit 1; }
+
+ - name: Run Dart format check
+ run: |
+ dart format --output=none --set-exit-if-changed lib/ test/ || \
+ { echo "Format check failed — run 'dart format lib/ test/' locally to fix."; exit 1; }
+
+ - name: Run Flutter analyze
+ run: flutter analyze --no-fatal-infos --no-fatal-warnings
+
+ # JOB 3 — Android debug build
+ build-android:
+ name: Android Debug Build
+ runs-on: ubuntu-latest
+ needs: analyze
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.41.4"
+ channel: stable
+ cache: true
+
+ - name: Set up Java (required for Android build)
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "17"
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Build Android debug APK
+ run: flutter build apk --debug --no-pub
+
+ - name: Confirm APK was produced
+ run: |
+ test -f build/app/outputs/flutter-apk/app-debug.apk || \
+ { echo "APK not found — build may have silently failed."; exit 1; }
+ echo "Android debug APK produced successfully."
+
+ - name: Upload APK artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: android-debug-apk
+ path: build/app/outputs/flutter-apk/app-debug.apk
+ retention-days: 7
diff --git a/.github/workflows/cross-main.yml b/.github/workflows/cross-main.yml
new file mode 100644
index 00000000..cb0a02d5
--- /dev/null
+++ b/.github/workflows/cross-main.yml
@@ -0,0 +1,202 @@
+name: Cross-Platform — Main Pipeline
+
+on:
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ # JOB 1 — Validate repo structure
+ validate-structure:
+ name: Validate Repo Structure
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Check pubspec files exist
+ run: |
+ test -f pubspec.yaml || { echo "MISSING: pubspec.yaml"; exit 1; }
+ test -f pubspec.lock || { echo "MISSING: pubspec.lock"; exit 1; }
+ echo "pubspec files present."
+
+ - name: Check analysis options exist
+ run: |
+ test -f analysis_options.yaml || { echo "MISSING: analysis_options.yaml"; exit 1; }
+ echo "analysis_options.yaml present."
+
+ - name: Check main entry point exists
+ run: |
+ test -f lib/main.dart || { echo "MISSING: lib/main.dart"; exit 1; }
+ echo "lib/main.dart present."
+
+ - name: Check core structure exists
+ run: |
+ test -d lib/core || { echo "MISSING: lib/core/"; exit 1; }
+ test -d lib/features || { echo "MISSING: lib/features/"; exit 1; }
+ test -f lib/core/network/api_client.dart || { echo "MISSING: lib/core/network/api_client.dart"; exit 1; }
+ test -f lib/core/routing/app_router.dart || { echo "MISSING: lib/core/routing/app_router.dart"; exit 1; }
+ echo "Core structure present."
+
+ - name: Check no hardcoded base URLs in API client
+ run: |
+ echo "Scanning api_client.dart for hardcoded URLs..."
+ if grep -qE "http://[0-9]|https://[a-zA-Z0-9]" lib/core/network/api_client.dart; then
+ echo "WARNING: Possible hardcoded URL found in api_client.dart"
+ grep -nE "http://[0-9]|https://[a-zA-Z0-9]" lib/core/network/api_client.dart || true
+ else
+ echo "No hardcoded URLs detected in api_client.dart."
+ fi
+
+ # JOB 2 — Flutter analysis and format check
+ analyze:
+ name: Format Check & Static Analysis
+ runs-on: ubuntu-latest
+ needs: validate-structure
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.41.4"
+ channel: stable
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Verify dependency resolution
+ run: |
+ flutter pub deps > /dev/null && echo "Dependencies resolved cleanly." || \
+ { echo "Dependency resolution failed."; exit 1; }
+
+ - name: Run Dart format check
+ run: |
+ dart format --output=none --set-exit-if-changed lib/ test/ || \
+ { echo "Format check failed — run 'dart format lib/ test/' locally to fix."; exit 1; }
+
+ - name: Run Flutter analyze
+ run: flutter analyze --no-fatal-infos
+
+ # JOB 3 — Unit and widget tests
+ test:
+ name: Unit & Widget Tests
+ runs-on: ubuntu-latest
+ needs: analyze
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.41.4"
+ channel: stable
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Run tests with coverage
+ run: flutter test --coverage
+
+ - name: Install lcov for coverage reporting
+ run: sudo apt-get install -y lcov
+
+ - name: Generate coverage summary
+ run: |
+ lcov --summary coverage/lcov.info 2>&1 | tee coverage/summary.txt
+ echo "Coverage summary generated."
+
+ - name: Enforce 95% line coverage threshold
+ run: |
+ echo "Checking coverage threshold..."
+ coverage=$(lcov --summary coverage/lcov.info 2>&1 | grep "lines" | grep -oP '[0-9]+\.[0-9]+(?=%)' | head -1)
+ echo "Current line coverage: ${coverage}%"
+ result=$(awk "BEGIN { print (${coverage} >= 95) ? \"pass\" : \"fail\" }")
+ if [ "$result" = "fail" ]; then
+ echo "Coverage threshold not met — ${coverage}% is below the required 95%."
+ echo "Raise test coverage in lib/ before merging to main."
+ exit 1
+ fi
+ echo "Coverage threshold passed — ${coverage}% meets the required 95%."
+
+ - name: Upload coverage report
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: flutter-coverage-report
+ path: |
+ coverage/lcov.info
+ coverage/summary.txt
+ retention-days: 7
+
+ # JOB 4 — Flutter integration tests
+ integration-tests:
+ name: Integration Tests
+ runs-on: ubuntu-latest
+ needs: test
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.41.4"
+ channel: stable
+ cache: true
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Run Flutter integration tests
+ run: flutter test integration_test
+
+ # JOB 5 — Android release build
+ build-android:
+ name: Android Release Build
+ runs-on: ubuntu-latest
+ needs: integration-tests
+
+ steps:
+ - name: Checkout cross-platform repo
+ uses: actions/checkout@v4
+
+ - name: Set up Flutter
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: "3.41.4"
+ channel: stable
+ cache: true
+
+ - name: Set up Java (required for Android build)
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: "17"
+
+ - name: Install dependencies
+ run: flutter pub get
+
+ - name: Build Android release APK
+ run: flutter build apk --release --no-pub
+
+ - name: Confirm release APK was produced
+ run: |
+ test -f build/app/outputs/flutter-apk/app-release.apk || \
+ { echo "Release APK not found — build may have silently failed."; exit 1; }
+ echo "Android release APK produced successfully."
+
+ - name: Upload release APK artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: android-release-apk
+ path: build/app/outputs/flutter-apk/app-release.apk
+ retention-days: 14
diff --git a/.gitignore b/.gitignore
index 3820a95c..75120c69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,30 @@ app.*.map.json
/android/app/debug
/android/app/profile
/android/app/release
+
+
+# Flutter generated plugin registrants
+**/generated_plugin_registrant.*
+**/GeneratedPluginRegistrant.*
+
+# Flutter plugin build files
+**/generated_plugins.cmake
+
+# Flutter plugin metadata
+.flutter-plugins
+.flutter-plugins-dependencies
+android/.kotlin/sessions/kotlin-compiler-13249507405906181982.salive
+macos/Flutter/GeneratedPluginRegistrant.swift
+windows/flutter/generated_plugin_registrant.cc
+windows/flutter/generated_plugins.cmake
+
+# Signing and Keystores
+key.properties
+*.keystore
+*.jks
+**/android/key.properties
+
+# Explicitly un-ignore debug.keystore
+!android/app/debug.keystore
+BUGFIX_SUMMARY.md
+WINDOWS_PLAYBACK_FIX.md
diff --git a/lib/config/.gitkeep b/.vscode/launch.json
similarity index 100%
rename from lib/config/.gitkeep
rename to .vscode/launch.json
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..b20e906e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+
+ "cmake.sourceDirectory": "D:/Coding/Rythmify/rythmify/linux"
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 9a709815..44423f1d 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,142 @@
-# cross
\ No newline at end of file
+# 🎵 Rythmify - Your Ultimate Music Experience
+
+[](https://flutter.dev)
+[](https://riverpod.dev)
+[](https://supabase.com)
+[](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html)
+
+**Rythmify** is a high-performance, feature-rich music streaming application built with Flutter. It combines a sleek UI with powerful audio capabilities, social features, and a robust backend to provide an immersive musical journey.
+
+---
+
+## ✨ Core Features
+
+### 🎧 Seamless Audio Experience
+- **Advanced Player**: Full-featured player with play/pause, seek, skip, and background playback support powered by `just_audio` and `audio_service`.
+- **High-Quality Streaming**: Optimized audio delivery for a crisp listening experience.
+- **Track Details**: Rich metadata, album art, and artist information.
+
+### 🌟 Social & Discovery
+- **Music Feed**: Stay updated with the latest releases and community trends.
+- **Messaging**: Built-in messaging system to share music and connect with friends.
+- **Comments & Likes**: Engage with tracks and artists through community discussions.
+- **Track Sharing**: Easily share your favorite music via social media (WhatsApp, etc.).
+
+### 📂 Library & Personalization
+- **Playlist Management**: Create, edit, and organize your custom playlists.
+- **User Profiles**: Personalized profiles with your music history and preferences.
+- **Search**: Fast and intuitive search for tracks, artists, and albums.
+- **Track Upload**: Empowering creators to upload and share their own music.
+
+### 🛡️ Secure & Scalable
+- **Authentication**: Secure login using Google Sign-In, Apple Sign-In, and standard auth providers.
+- **Real-time Sync**: Instant updates across devices using Supabase.
+- **Offline Support**: Local caching and storage using Hive and Shared Preferences.
+
+---
+
+## 🛠️ Technology Stack
+
+- **Frontend**: [Flutter](https://flutter.dev) (Dart)
+- **State Management**: [Riverpod](https://riverpod.dev) (Modular & Reactive)
+- **Backend**: [Supabase](https://supabase.com) (Auth, Database, Storage)
+- **Routing**: [GoRouter](https://pub.dev/packages/go_router)
+- **Audio Engine**: `just_audio` & `audio_service`
+- **Database (Local)**: Hive & Shared Preferences
+- **UI Components**: Material 3, Google Fonts, Shimmer effects, Lucide-style icons.
+
+---
+
+## 🏗️ Architecture
+
+Rythmify follows a **Clean Architecture** approach, ensuring the codebase is scalable, maintainable, and testable. The project is organized by **features**:
+
+```text
+lib/
+├── config/ # Application-wide configurations (Theme, Constants)
+├── core/ # Shared logic, utilities, and common UI components
+│ ├── data/ # Common data sources & repositories
+│ ├── domain/ # Shared entities & use cases
+│ ├── presentation/ # Shared widgets
+│ ├── routing/ # Navigation logic (GoRouter)
+│ └── services/ # Third-party service integrations
+└── features/ # Feature-specific modules
+ ├── authentication/
+ ├── player/
+ ├── track_upload/
+ ├── track/
+ ├── feed/
+ ├── comments/
+ ├── messaging/
+ ├── notifications/
+ ├── profile/
+ ├── playlist/
+ ├── library/
+ ├── search/
+ ├── settings/
+ └── premium/
+```
+
+Each feature module is further divided into `data`, `domain`, and `presentation` layers to maintain strict separation of concerns.
+
+---
+
+## 🚀 Getting Started
+
+### Prerequisites
+- [Flutter SDK](https://docs.flutter.dev/get-started/install) (3.11.1+)
+- [Dart SDK](https://dart.dev/get-started)
+- [Docker](https://www.docker.com/) (Optional, for local mock backend)
+
+### Installation
+
+1. **Clone the repository:**
+ ```bash
+ git clone https://github.com/your-username/rythmify.git
+ cd rythmify
+ ```
+
+2. **Install dependencies:**
+ ```bash
+ flutter pub get
+ ```
+
+3. **(Optional) Run local mock API:**
+ Rythmify uses a local `json-server` for development.
+ ```bash
+ docker-compose up -d
+ ```
+
+4. **Run the application:**
+ ```bash
+ flutter run
+ ```
+
+---
+
+## 📸 Screenshots
+
+| Onboarding | Home Feed | Music Player |
+| :---: | :---: | :---: |
+|  |  |  |
+
+---
+
+## 🤝 Contributing
+
+Contributions are welcome! Please follow these steps:
+1. Fork the Project.
+2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`).
+3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`).
+4. Push to the Branch (`git push origin feature/AmazingFeature`).
+5. Open a Pull Request.
+
+---
+
+## 📄 License
+
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
+
+---
+
+
Made with ❤️ for Music Lovers
diff --git a/analyze_output.txt b/analyze_output.txt
new file mode 100644
index 00000000..fb4b8162
Binary files /dev/null and b/analyze_output.txt differ
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index 78603cb8..5dbf33a6 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -1,16 +1,26 @@
+import java.util.Properties
+import java.io.FileInputStream
+
+val keyProperties = Properties()
+val keyPropertiesFile = rootProject.file("../key.properties")
+if (keyPropertiesFile.exists()) {
+ keyProperties.load(FileInputStream(keyPropertiesFile))
+}
+
plugins {
id("com.android.application")
id("kotlin-android")
- // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
+ id("com.google.gms.google-services")
}
android {
namespace = "com.example.rythmify"
- compileSdk = flutter.compileSdkVersion
+ compileSdk = 36
ndkVersion = flutter.ndkVersion
compileOptions {
+ isCoreLibraryDesugaringEnabled = true
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
@@ -19,21 +29,44 @@ android {
jvmTarget = JavaVersion.VERSION_17.toString()
}
+ signingConfigs {
+ create("release") {
+ keyAlias = keyProperties.getProperty("keyAlias")
+ keyPassword = keyProperties.getProperty("keyPassword")
+ storeFile = keyProperties.getProperty("storeFile")?.let { file(it) }
+ storePassword = keyProperties.getProperty("storePassword")
+ }
+ getByName("debug") {
+ val debugKeystore = file("debug.keystore")
+ if (debugKeystore.exists()) {
+ keyAlias = "androiddebugkey"
+ keyPassword = "android"
+ storeFile = debugKeystore
+ storePassword = "android"
+ }
+ }
+ }
+
defaultConfig {
- // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.rythmify"
- // You can update the following values to match your application needs.
- // For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
- targetSdk = flutter.targetSdkVersion
- versionCode = flutter.versionCode
- versionName = flutter.versionName
+ targetSdk = 36
+ versionCode = 1
+ versionName = "1.0.0"
}
+
buildTypes {
release {
- // TODO: Add your own signing config for the release build.
- // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig = signingConfigs.getByName("release")
+ isMinifyEnabled = true
+ isShrinkResources = true
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ debug {
signingConfig = signingConfigs.getByName("debug")
}
}
@@ -42,3 +75,8 @@ android {
flutter {
source = "../.."
}
+
+dependencies {
+ coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
+}
diff --git a/android/app/google-services.json b/android/app/google-services.json
new file mode 100644
index 00000000..e69b42eb
--- /dev/null
+++ b/android/app/google-services.json
@@ -0,0 +1,67 @@
+{
+ "project_info": {
+ "project_number": "456932364376",
+ "project_id": "my-project-5449-1773395739847",
+ "storage_bucket": "my-project-5449-1773395739847.firebasestorage.app"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:456932364376:android:f75848b793a8e34249fa2d",
+ "android_client_info": {
+ "package_name": "com.example.rythmify"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "456932364376-1jpo487l18fduhlvk7d0v3e016vumdsu.apps.googleusercontent.com",
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.rythmify",
+ "certificate_hash": "682460bf0e6a4539c35c39527555f0c0a69b0b1f"
+ }
+ },
+ {
+ "client_id": "456932364376-4q1tgrmle3g23m5r9rb7d1080fqnic2p.apps.googleusercontent.com",
+ "client_type": 1,
+ "android_info": {
+ "package_name": "com.example.rythmify",
+ "certificate_hash": "de755235b8efb8b7883a23bec0cd8a069cc96e6b"
+ }
+ },
+ {
+ "client_id": "456932364376-hmgop09n9k985moaq35vsppqn5ohvjdo.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyCu-omZIYEWFfq6gE1ZzPgXjchDmzW2LxA"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "456932364376-4ga0v16rd7dhemov4navlepcne4u51n8.apps.googleusercontent.com",
+ "client_type": 3
+ },
+ {
+ "client_id": "456932364376-6msrjr0ue7uqs84psq74p26neje7o1ct.apps.googleusercontent.com",
+ "client_type": 2,
+ "ios_info": {
+ "bundle_id": "com.example.rythmify"
+ }
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
+
+
+
+
+
diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro
new file mode 100644
index 00000000..3c85680b
--- /dev/null
+++ b/android/app/proguard-rules.pro
@@ -0,0 +1,33 @@
+# Flutter
+-keep class io.flutter.app.** { *; }
+-keep class io.flutter.plugin.** { *; }
+-keep class io.flutter.util.** { *; }
+-keep class io.flutter.view.** { *; }
+-keep class io.flutter.** { *; }
+-keep class io.flutter.plugins.** { *; }
+
+# Dio
+-keep class com.dio.** { *; }
+
+# Riverpod
+-keep class com.riverpod.** { *; }
+
+# GoRouter
+-keep class com.gorouter.** { *; }
+
+# Google Sign-In
+-keep class com.google.android.gms.auth.api.signin.** { *; }
+-keep class com.google.android.gms.auth.api.signin.internal.** { *; }
+
+# Firebase Auth
+-keep class com.google.firebase.auth.** { *; }
+
+# Preserve all model classes from obfuscation
+-keep class **.data.models.** { *; }
+-keep class **.domain.entities.** { *; }
+-keep class **.models.** { *; }
+-keep class **.entities.** { *; }
+
+# Flutter Play Core / Deferred Components
+-dontwarn com.google.android.play.core.**
+-dontwarn io.flutter.embedding.engine.deferredcomponents.**
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 4c2ecff3..fd0b1ca8 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,45 +1,247 @@
+
+
+
+
+
+
+
+
+ android:icon="@mipmap/ic_launcher"
+ android:usesCleartextTraffic="true">
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/kotlin/com/example/rythmify/LikesWidget.kt b/android/app/src/main/kotlin/com/example/rythmify/LikesWidget.kt
new file mode 100644
index 00000000..38c8a100
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/rythmify/LikesWidget.kt
@@ -0,0 +1,250 @@
+package com.example.rythmify
+
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffXfermode
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.view.View
+import android.widget.RemoteViews
+import es.antonborri.home_widget.HomeWidgetLaunchIntent
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.net.URL
+
+class LikesWidget : AppWidgetProvider() {
+
+ enum class LayoutType { ROW, FEATURE, MINI }
+
+ companion object {
+ private const val PREFS_NAME = "HomeWidgetPreferences"
+ const val MAX_TRACKS = 5
+
+ private val TRACK_VIEW_IDS = listOf(
+ R.id.likes_track_0, R.id.likes_track_1, R.id.likes_track_2,
+ R.id.likes_track_3, R.id.likes_track_4
+ )
+
+ fun triggerUpdate(context: Context) {
+ val manager = AppWidgetManager.getInstance(context)
+ val ids = manager.getAppWidgetIds(ComponentName(context, LikesWidget::class.java))
+ if (ids.isEmpty()) return
+ val intent = Intent(context, LikesWidget::class.java).apply {
+ action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
+ }
+ context.sendBroadcast(intent)
+ }
+
+ fun updateWidget(
+ context: Context,
+ manager: AppWidgetManager,
+ widgetId: Int,
+ // MAX values = actual available space; defaults assume a medium-size placement
+ width: Int = 300,
+ height: Int = 150
+ ) {
+ val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ val trackCount = minOf(prefs.getInt("likes_track_count", 0), MAX_TRACKS)
+ val userId = prefs.getString("likes_user_id", "") ?: ""
+ val pfpUrl = prefs.getString("likes_pfp_url", null)
+
+ val layout = chooseLayout(width, height)
+ val views = when (layout) {
+ LayoutType.MINI -> buildMiniViews(context, prefs, trackCount)
+ LayoutType.ROW -> buildRowViews(context, prefs, trackCount, userId, width)
+ LayoutType.FEATURE -> buildFeatureViews(context, prefs, trackCount, userId)
+ }
+
+ manager.updateAppWidget(widgetId, views)
+
+ // Async: fetch bitmaps and re-draw
+ CoroutineScope(Dispatchers.IO).launch {
+ val pfpBitmap = if (!pfpUrl.isNullOrBlank()) loadCircleBitmap(pfpUrl) else null
+ val artUrls = (0 until trackCount).map { i ->
+ prefs.getString("likes_track_${i}_art", null)
+ }
+ val artBitmaps = artUrls.map { url ->
+ if (!url.isNullOrBlank()) loadBitmap(url) else null
+ }
+
+ withContext(Dispatchers.Main) {
+ when (layout) {
+ LayoutType.ROW -> {
+ pfpBitmap?.let { views.setImageViewBitmap(R.id.likes_pfp, it) }
+ val n = tracksToShow(width, trackCount)
+ artBitmaps.forEachIndexed { i, bmp ->
+ if (i < n && bmp != null) {
+ views.setImageViewBitmap(TRACK_VIEW_IDS[i], bmp)
+ }
+ }
+ }
+ LayoutType.FEATURE -> {
+ pfpBitmap?.let { views.setImageViewBitmap(R.id.likes_pfp, it) }
+ artBitmaps.firstOrNull()?.let {
+ views.setImageViewBitmap(R.id.likes_feature_art, it)
+ }
+ }
+ LayoutType.MINI -> {
+ artBitmaps.firstOrNull()?.let {
+ views.setImageViewBitmap(R.id.likes_mini_art, it)
+ }
+ }
+ }
+ manager.updateAppWidget(widgetId, views)
+ }
+ }
+ }
+
+ // width/height = MAX dimensions (available space) from the options bundle.
+ // ROW: compact height (1 cell ≈ 60-100dp). MINI: narrow width (1 cell ≈ 60-100dp).
+ private fun chooseLayout(width: Int, height: Int): LayoutType = when {
+ width < 130 -> LayoutType.MINI
+ height < 130 -> LayoutType.ROW
+ else -> LayoutType.FEATURE
+ }
+
+ // On a 360dp screen the 4-cell full-width widget is ~288dp — well under 300.
+ // Lower thresholds to match real device grid sizes.
+ private fun tracksToShow(width: Int, totalCount: Int): Int {
+ val maxByWidth = when {
+ width >= 250 -> 5
+ width >= 160 -> 2
+ else -> 1
+ }
+ return minOf(maxByWidth, totalCount)
+ }
+
+ private fun buildRowViews(
+ context: Context,
+ prefs: android.content.SharedPreferences,
+ trackCount: Int,
+ userId: String,
+ width: Int
+ ): RemoteViews {
+ val views = RemoteViews(context.packageName, R.layout.likes_widget_row)
+ val n = tracksToShow(width, trackCount)
+
+ // views.setOnClickPendingIntent(R.id.likes_root, openLikesIntent(context))
+ // views.setOnClickPendingIntent(R.id.likes_pfp, openProfileIntent(context, userId))
+
+ TRACK_VIEW_IDS.forEachIndexed { i, viewId ->
+ if (i < n) {
+ views.setViewVisibility(viewId, View.VISIBLE)
+ // val trackId = prefs.getString("likes_track_${i}_id", null)
+ // views.setOnClickPendingIntent(viewId, trackIntent(context, trackId))
+ } else {
+ views.setViewVisibility(viewId, View.GONE)
+ }
+ }
+
+ return views
+ }
+
+ private fun buildFeatureViews(
+ context: Context,
+ prefs: android.content.SharedPreferences,
+ trackCount: Int,
+ userId: String
+ ): RemoteViews {
+ val views = RemoteViews(context.packageName, R.layout.likes_widget_feature)
+
+ // views.setOnClickPendingIntent(R.id.likes_root, openLikesIntent(context))
+ // views.setOnClickPendingIntent(R.id.likes_pfp, openProfileIntent(context, userId))
+
+ // val trackId = if (trackCount > 0) prefs.getString("likes_track_0_id", null) else null
+ // views.setOnClickPendingIntent(R.id.likes_feature_art, trackIntent(context, trackId))
+
+ return views
+ }
+
+ private fun buildMiniViews(
+ context: Context,
+ prefs: android.content.SharedPreferences,
+ trackCount: Int
+ ): RemoteViews {
+ val views = RemoteViews(context.packageName, R.layout.likes_widget_mini)
+
+ // views.setOnClickPendingIntent(R.id.likes_root, openLikesIntent(context))
+
+ // val trackId = if (trackCount > 0) prefs.getString("likes_track_0_id", null) else null
+ // views.setOnClickPendingIntent(R.id.likes_mini_art, trackIntent(context, trackId))
+
+ return views
+ }
+
+ private fun openLikesIntent(context: Context): PendingIntent =
+ HomeWidgetLaunchIntent.getActivity(
+ context, MainActivity::class.java,
+ Uri.parse("rythmify://likes_open")
+ )
+
+ private fun openProfileIntent(context: Context, userId: String): PendingIntent =
+ HomeWidgetLaunchIntent.getActivity(
+ context, MainActivity::class.java,
+ Uri.parse("rythmify://likes_profile?userId=${Uri.encode(userId)}")
+ )
+
+ private fun trackIntent(context: Context, trackId: String?): PendingIntent {
+ val uri = if (!trackId.isNullOrBlank())
+ Uri.parse("rythmify://likes_track?id=${Uri.encode(trackId)}")
+ else Uri.parse("rythmify://likes_open")
+ return HomeWidgetLaunchIntent.getActivity(context, MainActivity::class.java, uri)
+ }
+
+ private fun loadBitmap(url: String): Bitmap? = try {
+ BitmapFactory.decodeStream(URL(url).openStream())
+ } catch (_: Exception) {
+ null
+ }
+
+ private fun loadCircleBitmap(url: String): Bitmap? {
+ val bmp = loadBitmap(url) ?: return null
+ val size = minOf(bmp.width, bmp.height)
+ val output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
+ val canvas = Canvas(output)
+ val paint = Paint(Paint.ANTI_ALIAS_FLAG)
+ canvas.drawCircle(size / 2f, size / 2f, size / 2f, paint)
+ paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
+ val scaled = Bitmap.createScaledBitmap(bmp, size, size, true)
+ canvas.drawBitmap(scaled, 0f, 0f, paint)
+ return output
+ }
+ }
+
+ override fun onUpdate(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetIds: IntArray
+ ) {
+ appWidgetIds.forEach { id ->
+ val opts = appWidgetManager.getAppWidgetOptions(id)
+ val w = opts.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 300)
+ val h = opts.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 150)
+ updateWidget(context, appWidgetManager, id, w, h)
+ }
+ }
+
+ override fun onAppWidgetOptionsChanged(
+ context: Context,
+ appWidgetManager: AppWidgetManager,
+ appWidgetId: Int,
+ newOptions: Bundle
+ ) {
+ val w = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH, 300)
+ val h = newOptions.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 150)
+ updateWidget(context, appWidgetManager, appWidgetId, w, h)
+ }
+}
diff --git a/android/app/src/main/kotlin/com/example/rythmify/MainActivity.kt b/android/app/src/main/kotlin/com/example/rythmify/MainActivity.kt
index ad02c01c..610f36f6 100644
--- a/android/app/src/main/kotlin/com/example/rythmify/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/example/rythmify/MainActivity.kt
@@ -1,5 +1,134 @@
package com.example.rythmify
-import io.flutter.embedding.android.FlutterActivity
+import android.appwidget.AppWidgetManager
+import android.content.ComponentName
+import android.content.pm.PackageManager
+import android.os.Build
+import com.ryanheise.audioservice.AudioServiceActivity
+import io.flutter.embedding.engine.FlutterEngine
+import io.flutter.plugin.common.MethodChannel
-class MainActivity : FlutterActivity()
+class MainActivity : AudioServiceActivity() {
+
+ private val CHANNEL = "flutter_dynamic_icon"
+ private val MAIN_ACTIVITY = ".MainActivity"
+ private val DEFAULT_ALIAS = ".MainActivity.default"
+
+ override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
+ super.configureFlutterEngine(flutterEngine)
+
+ MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
+ val pkg = packageName
+ val pm = packageManager
+ when (call.method) {
+ "mSupportsAlternateIcons" -> result.success(true)
+ "mSetAlternateIconName" -> {
+ try {
+ setIcon(pm, pkg, call.argument("iconName"))
+ result.success(null)
+ } catch (e: Exception) {
+ result.error("ERROR", e.message, null)
+ }
+ }
+ "mGetAlternateIconName" -> {
+ try {
+ result.success(getCurrentIcon(pm, pkg))
+ } catch (e: Exception) {
+ result.error("ERROR", e.message, null)
+ }
+ }
+ "mGetAvailableIcons" -> {
+ try {
+ result.success(getAvailableIcons(pm, pkg))
+ } catch (e: Exception) {
+ result.error("ERROR", e.message, null)
+ }
+ }
+ "pinWidget" -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val manager = AppWidgetManager.getInstance(this)
+ if (manager.isRequestPinAppWidgetSupported) {
+ val component = ComponentName(this, PlayerWidget::class.java)
+ manager.requestPinAppWidget(component, null, null)
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ } else {
+ result.success(false)
+ }
+ }
+ "pinLikesWidget" -> {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ val manager = AppWidgetManager.getInstance(this)
+ if (manager.isRequestPinAppWidgetSupported) {
+ val component = ComponentName(this, LikesWidget::class.java)
+ manager.requestPinAppWidget(component, null, null)
+ result.success(true)
+ } else {
+ result.success(false)
+ }
+ } else {
+ result.success(false)
+ }
+ }
+ else -> result.notImplemented()
+ }
+ }
+ }
+
+ private fun getAvailableIcons(pm: PackageManager, pkg: String): List {
+ val intent = android.content.Intent(android.content.Intent.ACTION_MAIN).apply {
+ addCategory(android.content.Intent.CATEGORY_LAUNCHER)
+ setPackage(pkg)
+ }
+ val prefix = "$pkg$MAIN_ACTIVITY."
+ return pm.queryIntentActivities(intent, PackageManager.GET_DISABLED_COMPONENTS)
+ .map { it.activityInfo.name }
+ .filter { it.startsWith(prefix) }
+ .map { it.removePrefix(prefix) }
+ .filter { it != "default" }
+ }
+
+ private fun getCurrentIcon(pm: PackageManager, pkg: String): String? {
+ val defaultAlias = ComponentName(pkg, "$pkg$DEFAULT_ALIAS")
+ try {
+ if (pm.getComponentEnabledSetting(defaultAlias) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+ return null
+ } catch (_: Exception) {}
+ getAvailableIcons(pm, pkg).forEach { name ->
+ val c = ComponentName(pkg, "$pkg$MAIN_ACTIVITY.$name")
+ if (pm.getComponentEnabledSetting(c) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+ return name
+ }
+ return null
+ }
+
+ private fun setIcon(pm: PackageManager, pkg: String, iconName: String?) {
+ val mainActivity = ComponentName(pkg, "$pkg$MAIN_ACTIVITY")
+ val defaultAlias = ComponentName(pkg, "$pkg$DEFAULT_ALIAS")
+ val available = getAvailableIcons(pm, pkg)
+
+ pm.setComponentEnabledSetting(mainActivity, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)
+ pm.setComponentEnabledSetting(defaultAlias, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP)
+ available.forEach { name ->
+ pm.setComponentEnabledSetting(
+ ComponentName(pkg, "$pkg$MAIN_ACTIVITY.$name"),
+ PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ }
+
+ if (iconName.isNullOrEmpty()) {
+ pm.setComponentEnabledSetting(defaultAlias, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP)
+ } else {
+ if (!available.contains(iconName))
+ throw IllegalArgumentException("Icon '$iconName' not found")
+ pm.setComponentEnabledSetting(
+ ComponentName(pkg, "$pkg$MAIN_ACTIVITY.$iconName"),
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+ PackageManager.DONT_KILL_APP
+ )
+ }
+ }
+}
diff --git a/android/app/src/main/kotlin/com/example/rythmify/PlayerWidget.kt b/android/app/src/main/kotlin/com/example/rythmify/PlayerWidget.kt
new file mode 100644
index 00000000..70eafe54
--- /dev/null
+++ b/android/app/src/main/kotlin/com/example/rythmify/PlayerWidget.kt
@@ -0,0 +1,137 @@
+package com.example.rythmify
+
+import android.app.PendingIntent
+import android.appwidget.AppWidgetManager
+import android.appwidget.AppWidgetProvider
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.net.Uri
+import android.os.Build
+import android.view.KeyEvent
+import android.widget.RemoteViews
+import es.antonborri.home_widget.HomeWidgetLaunchIntent
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.net.URL
+
+class PlayerWidget : AppWidgetProvider() {
+
+ companion object {
+ const val ACTION_PREV = "com.example.rythmify.WIDGET_PREV"
+ const val ACTION_PLAY_PAUSE = "com.example.rythmify.WIDGET_PLAY_PAUSE"
+ const val ACTION_NEXT = "com.example.rythmify.WIDGET_NEXT"
+
+ // home_widget writes to this SharedPreferences file
+ private const val PREFS_NAME = "HomeWidgetPreferences"
+
+ fun triggerUpdate(context: Context) {
+ val manager = AppWidgetManager.getInstance(context)
+ val ids = manager.getAppWidgetIds(ComponentName(context, PlayerWidget::class.java))
+ if (ids.isEmpty()) return
+ val intent = Intent(context, PlayerWidget::class.java).apply {
+ action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
+ putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids)
+ }
+ context.sendBroadcast(intent)
+ }
+
+ fun updateWidget(context: Context, manager: AppWidgetManager, widgetId: Int) {
+ val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ val title = prefs.getString("track_title", "No track playing") ?: "No track playing"
+ val artist = prefs.getString("artist_name", "") ?: ""
+ val isPlaying = prefs.getBoolean("is_playing", false)
+ val isLiked = prefs.getBoolean("is_liked", false)
+ val artUrl = prefs.getString("art_url", null)
+
+ val views = RemoteViews(context.packageName, R.layout.player_widget)
+ views.setTextViewText(R.id.widget_artist, artist)
+ views.setTextViewText(R.id.widget_title, title)
+ views.setImageViewResource(
+ R.id.widget_btn_play_pause,
+ if (isPlaying) R.drawable.ic_widget_pause else R.drawable.ic_widget_play
+ )
+ views.setImageViewResource(
+ R.id.widget_btn_like,
+ if (isLiked) R.drawable.ic_favorite else R.drawable.ic_favorite_border
+ )
+
+ val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+ PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
+ else
+ PendingIntent.FLAG_UPDATE_CURRENT
+
+ views.setOnClickPendingIntent(R.id.widget_btn_prev, broadcast(context, ACTION_PREV, flags))
+ views.setOnClickPendingIntent(R.id.widget_btn_play_pause, broadcast(context, ACTION_PLAY_PAUSE, flags))
+ views.setOnClickPendingIntent(R.id.widget_btn_next, broadcast(context, ACTION_NEXT, flags))
+
+ val likeIntent = HomeWidgetLaunchIntent.getActivity(
+ context,
+ MainActivity::class.java,
+ Uri.parse("rythmify://widget?action=like")
+ )
+ views.setOnClickPendingIntent(R.id.widget_btn_like, likeIntent)
+
+ val openPlayerIntent = HomeWidgetLaunchIntent.getActivity(
+ context,
+ MainActivity::class.java,
+ Uri.parse("rythmify://player")
+ )
+ views.setOnClickPendingIntent(R.id.widget_album_art, openPlayerIntent)
+ views.setOnClickPendingIntent(R.id.widget_text_area, openPlayerIntent)
+
+ manager.updateAppWidget(widgetId, views)
+
+ // Async album art load — update again once the bitmap is ready
+ if (!artUrl.isNullOrBlank()) {
+ CoroutineScope(Dispatchers.IO).launch {
+ val bitmap = loadBitmap(artUrl)
+ if (bitmap != null) {
+ withContext(Dispatchers.Main) {
+ views.setImageViewBitmap(R.id.widget_album_art, bitmap)
+ manager.updateAppWidget(widgetId, views)
+ }
+ }
+ }
+ }
+ }
+
+ private fun broadcast(context: Context, action: String, flags: Int): PendingIntent {
+ val intent = Intent(context, PlayerWidget::class.java).apply { this.action = action }
+ return PendingIntent.getBroadcast(context, action.hashCode(), intent, flags)
+ }
+
+ private fun loadBitmap(url: String): Bitmap? = try {
+ BitmapFactory.decodeStream(URL(url).openStream())
+ } catch (_: Exception) {
+ null
+ }
+ }
+
+ override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
+ appWidgetIds.forEach { updateWidget(context, appWidgetManager, it) }
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ super.onReceive(context, intent)
+ when (intent.action) {
+ ACTION_PREV -> mediaKey(context, KeyEvent.KEYCODE_MEDIA_PREVIOUS)
+ ACTION_PLAY_PAUSE -> mediaKey(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)
+ ACTION_NEXT -> mediaKey(context, KeyEvent.KEYCODE_MEDIA_NEXT)
+ }
+ }
+
+ private fun mediaKey(context: Context, keyCode: Int) {
+ for (action in listOf(KeyEvent.ACTION_DOWN, KeyEvent.ACTION_UP)) {
+ val intent = Intent(context, com.ryanheise.audioservice.MediaButtonReceiver::class.java).apply {
+ this.action = Intent.ACTION_MEDIA_BUTTON
+ putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(action, keyCode))
+ }
+ context.sendBroadcast(intent)
+ }
+ }
+}
diff --git a/android/app/src/main/res/drawable-hdpi/android12splash.png b/android/app/src/main/res/drawable-hdpi/android12splash.png
new file mode 100644
index 00000000..3880ae7f
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..229f7bdf
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-hdpi/splash.png b/android/app/src/main/res/drawable-hdpi/splash.png
new file mode 100644
index 00000000..3880ae7f
Binary files /dev/null and b/android/app/src/main/res/drawable-hdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/android12splash.png b/android/app/src/main/res/drawable-mdpi/android12splash.png
new file mode 100644
index 00000000..e1f92cd1
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..83acfce4
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/splash.png b/android/app/src/main/res/drawable-mdpi/splash.png
new file mode 100644
index 00000000..e1f92cd1
Binary files /dev/null and b/android/app/src/main/res/drawable-mdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-night-hdpi/android12splash.png b/android/app/src/main/res/drawable-night-hdpi/android12splash.png
new file mode 100644
index 00000000..3880ae7f
Binary files /dev/null and b/android/app/src/main/res/drawable-night-hdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-night-mdpi/android12splash.png b/android/app/src/main/res/drawable-night-mdpi/android12splash.png
new file mode 100644
index 00000000..e1f92cd1
Binary files /dev/null and b/android/app/src/main/res/drawable-night-mdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-night-xhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xhdpi/android12splash.png
new file mode 100644
index 00000000..3b3d3bce
Binary files /dev/null and b/android/app/src/main/res/drawable-night-xhdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png
new file mode 100644
index 00000000..1c376db8
Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png b/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png
new file mode 100644
index 00000000..141a1dff
Binary files /dev/null and b/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-v21/background.png b/android/app/src/main/res/drawable-v21/background.png
new file mode 100644
index 00000000..75b99f0e
Binary files /dev/null and b/android/app/src/main/res/drawable-v21/background.png differ
diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml
index f74085f3..3cc4948a 100644
--- a/android/app/src/main/res/drawable-v21/launch_background.xml
+++ b/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -1,12 +1,9 @@
-
-
-
-
-
+ -
+
+
+ -
+
+
diff --git a/android/app/src/main/res/drawable-xhdpi/android12splash.png b/android/app/src/main/res/drawable-xhdpi/android12splash.png
new file mode 100644
index 00000000..3b3d3bce
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..6c67007d
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/splash.png b/android/app/src/main/res/drawable-xhdpi/splash.png
new file mode 100644
index 00000000..3b3d3bce
Binary files /dev/null and b/android/app/src/main/res/drawable-xhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/android12splash.png b/android/app/src/main/res/drawable-xxhdpi/android12splash.png
new file mode 100644
index 00000000..1c376db8
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..e37a3b80
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/splash.png b/android/app/src/main/res/drawable-xxhdpi/splash.png
new file mode 100644
index 00000000..1c376db8
Binary files /dev/null and b/android/app/src/main/res/drawable-xxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/android12splash.png b/android/app/src/main/res/drawable-xxxhdpi/android12splash.png
new file mode 100644
index 00000000..141a1dff
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/android12splash.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..cae3f209
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/splash.png b/android/app/src/main/res/drawable-xxxhdpi/splash.png
new file mode 100644
index 00000000..141a1dff
Binary files /dev/null and b/android/app/src/main/res/drawable-xxxhdpi/splash.png differ
diff --git a/android/app/src/main/res/drawable/background.png b/android/app/src/main/res/drawable/background.png
new file mode 100644
index 00000000..75b99f0e
Binary files /dev/null and b/android/app/src/main/res/drawable/background.png differ
diff --git a/android/app/src/main/res/drawable/ic_favorite.xml b/android/app/src/main/res/drawable/ic_favorite.xml
new file mode 100644
index 00000000..0823ed14
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_favorite.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_favorite_border.xml b/android/app/src/main/res/drawable/ic_favorite_border.xml
new file mode 100644
index 00000000..55886aa6
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_favorite_border.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_widget_pause.xml b/android/app/src/main/res/drawable/ic_widget_pause.xml
new file mode 100644
index 00000000..d455dec8
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_pause.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_widget_play.xml b/android/app/src/main/res/drawable/ic_widget_play.xml
new file mode 100644
index 00000000..425a4e25
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_play.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_widget_skip_next.xml b/android/app/src/main/res/drawable/ic_widget_skip_next.xml
new file mode 100644
index 00000000..5fd8cb78
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_skip_next.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/ic_widget_skip_previous.xml b/android/app/src/main/res/drawable/ic_widget_skip_previous.xml
new file mode 100644
index 00000000..e9bac0bb
--- /dev/null
+++ b/android/app/src/main/res/drawable/ic_widget_skip_previous.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml
index 304732f8..3cc4948a 100644
--- a/android/app/src/main/res/drawable/launch_background.xml
+++ b/android/app/src/main/res/drawable/launch_background.xml
@@ -1,12 +1,9 @@
-
-
-
-
-
+ -
+
+
+ -
+
+
diff --git a/android/app/src/main/res/drawable/likes_widget_background.xml b/android/app/src/main/res/drawable/likes_widget_background.xml
new file mode 100644
index 00000000..4cbeba75
--- /dev/null
+++ b/android/app/src/main/res/drawable/likes_widget_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/drawable/logo.png b/android/app/src/main/res/drawable/logo.png
new file mode 100644
index 00000000..3db11ad2
Binary files /dev/null and b/android/app/src/main/res/drawable/logo.png differ
diff --git a/android/app/src/main/res/drawable/widget_background.xml b/android/app/src/main/res/drawable/widget_background.xml
new file mode 100644
index 00000000..dbd405ca
--- /dev/null
+++ b/android/app/src/main/res/drawable/widget_background.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/likes_widget_feature.xml b/android/app/src/main/res/layout/likes_widget_feature.xml
new file mode 100644
index 00000000..3c684303
--- /dev/null
+++ b/android/app/src/main/res/layout/likes_widget_feature.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/likes_widget_mini.xml b/android/app/src/main/res/layout/likes_widget_mini.xml
new file mode 100644
index 00000000..142c71e7
--- /dev/null
+++ b/android/app/src/main/res/layout/likes_widget_mini.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/likes_widget_row.xml b/android/app/src/main/res/layout/likes_widget_row.xml
new file mode 100644
index 00000000..eb71b6e8
--- /dev/null
+++ b/android/app/src/main/res/layout/likes_widget_row.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/layout/player_widget.xml b/android/app/src/main/res/layout/player_widget.xml
new file mode 100644
index 00000000..d78bd1bf
--- /dev/null
+++ b/android/app/src/main/res/layout/player_widget.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..c79c58a3
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_chrome.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_chrome.xml
new file mode 100644
index 00000000..84ba1b52
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_chrome.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hot_pink.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hot_pink.xml
new file mode 100644
index 00000000..8d67d941
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_hot_pink.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_leopard.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_leopard.xml
new file mode 100644
index 00000000..2e8c8143
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_leopard.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_og.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_og.xml
new file mode 100644
index 00000000..0660e3c8
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_og.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_gold.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_gold.xml
new file mode 100644
index 00000000..c318ebbb
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_rose_gold.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_silver.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_silver.xml
new file mode 100644
index 00000000..0765d55b
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_silver.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_soft_purple.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_soft_purple.xml
new file mode 100644
index 00000000..aa7f15cd
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_soft_purple.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_tie_dye.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_tie_dye.xml
new file mode 100644
index 00000000..459b41d0
--- /dev/null
+++ b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_tie_dye.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index db77bb4b..38055bb0 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_chrome.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_chrome.png
new file mode 100644
index 00000000..98e46c3e
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_chrome.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_hot_pink.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_hot_pink.png
new file mode 100644
index 00000000..c8f3f679
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_hot_pink.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_leopard.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_leopard.png
new file mode 100644
index 00000000..d840d7de
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_leopard.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_og.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_og.png
new file mode 100644
index 00000000..694795d6
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_og.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_og_foreground.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_og_foreground.png
new file mode 100644
index 00000000..d1ac74b7
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_og_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_rose_gold.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_rose_gold.png
new file mode 100644
index 00000000..c6ee00fd
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_rose_gold.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_silver.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_silver.png
new file mode 100644
index 00000000..a58b4b1a
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_silver.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_soft_purple.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_soft_purple.png
new file mode 100644
index 00000000..c6956168
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_soft_purple.png differ
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_tie_dye.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher_tie_dye.png
new file mode 100644
index 00000000..f67c8050
Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher_tie_dye.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 17987b79..7aaac4dd 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_chrome.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_chrome.png
new file mode 100644
index 00000000..3596b3ef
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_chrome.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_hot_pink.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_hot_pink.png
new file mode 100644
index 00000000..fbdacd42
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_hot_pink.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_leopard.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_leopard.png
new file mode 100644
index 00000000..c2adbd9b
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_leopard.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_og.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_og.png
new file mode 100644
index 00000000..f7ed9610
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_og.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_og_foreground.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_og_foreground.png
new file mode 100644
index 00000000..e6e95af9
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_og_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_rose_gold.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_rose_gold.png
new file mode 100644
index 00000000..773d6484
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_rose_gold.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_silver.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_silver.png
new file mode 100644
index 00000000..0177565e
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_silver.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_soft_purple.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_soft_purple.png
new file mode 100644
index 00000000..37ff6d46
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_soft_purple.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_tie_dye.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher_tie_dye.png
new file mode 100644
index 00000000..3823404e
Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher_tie_dye.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 09d43914..cbfa6427 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_chrome.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_chrome.png
new file mode 100644
index 00000000..43a1eaa9
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_chrome.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_hot_pink.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_hot_pink.png
new file mode 100644
index 00000000..22aa848a
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_hot_pink.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_leopard.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_leopard.png
new file mode 100644
index 00000000..9d08d64f
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_leopard.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_og.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_og.png
new file mode 100644
index 00000000..37ed12b7
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_og.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_og_foreground.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_og_foreground.png
new file mode 100644
index 00000000..0ca10546
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_og_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_rose_gold.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_rose_gold.png
new file mode 100644
index 00000000..e10ff149
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_rose_gold.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_silver.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_silver.png
new file mode 100644
index 00000000..bb8004bb
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_silver.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_soft_purple.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_soft_purple.png
new file mode 100644
index 00000000..6fce505b
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_soft_purple.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_tie_dye.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_tie_dye.png
new file mode 100644
index 00000000..29b0fc91
Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_tie_dye.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index d5f1c8d3..376af04a 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_chrome.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_chrome.png
new file mode 100644
index 00000000..ffd812c5
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_chrome.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_hot_pink.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_hot_pink.png
new file mode 100644
index 00000000..1b3056b4
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_hot_pink.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_leopard.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_leopard.png
new file mode 100644
index 00000000..312ca99f
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_leopard.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_og.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_og.png
new file mode 100644
index 00000000..20e845c4
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_og.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_og_foreground.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_og_foreground.png
new file mode 100644
index 00000000..b3df4555
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_og_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_rose_gold.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_rose_gold.png
new file mode 100644
index 00000000..ad1b4538
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_rose_gold.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_silver.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_silver.png
new file mode 100644
index 00000000..e5b330ef
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_silver.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_soft_purple.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_soft_purple.png
new file mode 100644
index 00000000..e32e569f
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_soft_purple.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_tie_dye.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_tie_dye.png
new file mode 100644
index 00000000..b331ea88
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_tie_dye.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 4d6372ee..23b488fd 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_chrome.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_chrome.png
new file mode 100644
index 00000000..786a05d9
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_chrome.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_hot_pink.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_hot_pink.png
new file mode 100644
index 00000000..5290ede8
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_hot_pink.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_leopard.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_leopard.png
new file mode 100644
index 00000000..58571bb3
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_leopard.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_og.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_og.png
new file mode 100644
index 00000000..3e5c80cc
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_og.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_og_foreground.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_og_foreground.png
new file mode 100644
index 00000000..520cbb64
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_og_foreground.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_rose_gold.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_rose_gold.png
new file mode 100644
index 00000000..a21acd7d
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_rose_gold.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_silver.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_silver.png
new file mode 100644
index 00000000..8ab68922
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_silver.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_soft_purple.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_soft_purple.png
new file mode 100644
index 00000000..a533140c
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_soft_purple.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_tie_dye.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_tie_dye.png
new file mode 100644
index 00000000..172b791a
Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_tie_dye.png differ
diff --git a/android/app/src/main/res/values-night-v31/styles.xml b/android/app/src/main/res/values-night-v31/styles.xml
new file mode 100644
index 00000000..896b23df
--- /dev/null
+++ b/android/app/src/main/res/values-night-v31/styles.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml
index 06952be7..dbc9ea9f 100644
--- a/android/app/src/main/res/values-night/styles.xml
+++ b/android/app/src/main/res/values-night/styles.xml
@@ -5,6 +5,10 @@
- @drawable/launch_background
+ - false
+ - false
+ - false
+ - shortEdges
+
+
+
+
diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml
new file mode 100644
index 00000000..ca9cb769
--- /dev/null
+++ b/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,12 @@
+
+
+ #121212
+ #FF5500
+ #78909C
+ #C5848F
+ #A8A9AD
+ #A78BFA
+ #FF3EA5
+ #4ECDC4
+ #8B5E3C
+
\ No newline at end of file
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
new file mode 100644
index 00000000..aed7c4a6
--- /dev/null
+++ b/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ Control what\'s playing on Rythmify
+ Quickly access your latest liked tracks
+
diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml
index cb1ef880..0d1fa8fc 100644
--- a/android/app/src/main/res/values/styles.xml
+++ b/android/app/src/main/res/values/styles.xml
@@ -5,6 +5,10 @@
- @drawable/launch_background
+ - false
+ - false
+ - false
+ - shortEdges
+
+
+
+
+
+
+
+ Gradle Configuration Cache
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
+