Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
node_modules/
android/.gradle/
android/app/build/
android/app/.cxx/
ios/Pods/
ios/build/
ios/.xcode.env.local
Expand All @@ -10,6 +11,9 @@ ios/.xcode.env.local
*.p8
*.p12

# Local gradle properties with secrets
android/gradle-local.properties

# Builds
dist/

Expand Down
75 changes: 66 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,92 @@ A React Native app for streaming WMBR 88.1 FM and browsing show archives, song h
### Prerequisites
- Complete the [React Native environment setup](https://reactnative.dev/docs/set-up-your-environment)
- Install dependencies: `npm install`
- Install iOS pods: `cd ios && bundle exec pod install`

## Create your local environment file
- Copy `ios/.xcode.env` to `ios/.xcode.env.local` and modify the node path.
## Build and Run

## Build the app
### iOS
- Install iOS pods: `cd ios && bundle exec pod install`
- Copy `ios/.xcode.env` to `ios/.xcode.env.local` and modify the node path
- Open the app in Xcode:
```bash
open ios/WMBRRadioApp.xcworkspace
```
- Build the app (⇧⌘B).
- Build the app (⇧⌘B)
- Run iOS simulator:
```bash
npm run ios -- --simulator="iPhone 16"
```

### Android
- Start Android emulator via Android Studio or connect physical device
- Enable USB debugging on physical devices
- Run debug build:
```bash
npm run android
```

## Building for Release

### Android Release Setup

- Get `wmbr-upload-key.keystore` file and place in `android/` directory
- Set up signing configuration:
```bash
cd android
cp gradle-local.properties.default gradle-local.properties
```
- Edit `gradle-local.properties` with keystore credentials (passwords and alias)

### Build Release AAB

### Run the Simulator
**Windows:**
```bash
npm run ios -- --simulator="iPhone 16"
cd android && gradlew bundleRelease
```

That's it! The app will launch in the iPhone 16 simulator.
**macOS/Linux:**
```bash
cd android && ./gradlew bundleRelease
```

Output: `android/app/build/outputs/bundle/release/app-release.aab`

## Development

- **Hot reload**: Save any file to see changes instantly
- **Restart**: Press `R` in the simulator to reload
- **Debug menu**: Press `Cmd + D` in simulator for debug options

## Troubleshooting

### Kotlin 2.1.x Compatibility Fix

This project uses a patch-package solution to fix compatibility issues between `react-native-track-player` and Kotlin 2.1.x. The original package had TurboModule compatibility issues with newer Kotlin versions causing build failures.

**Solution Steps**:
1. Modified the problematic Kotlin files in `node_modules/react-native-track-player/`
2. Generated the patch file using `npx patch-package react-native-track-player`
3. Patches are automatically applied during `npm install` via the `postinstall` script
4. No manual intervention required - the fixes are version controlled in the `patches/` directory

**Modifications made to react-native-track-player:**

**In `MusicModule.kt`:**
- Added a wrapper function `launchInScope()` to fix coroutine scope issues with Kotlin 2.1.x
- Replaced all `scope.launch` calls with `launchInScope` calls
- Fixed nullable bundle handling by adding `?: Bundle()` fallbacks
- Changed all `return@launch` to `return@launchInScope` for consistency

**In `MusicService.kt`:**
- Fixed the `onBind()` method signature to handle nullable Intent parameter properly

**If you encounter build errors with react-native-track-player:**
- Ensure patches are applied: `npx patch-package`
- Check that `patches/react-native-track-player+4.1.1.patch` exists
- Verify `postinstall` script is in package.json

## Architecture

- **Audio Streaming**: React Native Track Player
- **State Management**: React hooks and context
- **UI**: React Native with custom animations
- **APIs**: WMBR metadata and archive services
21 changes: 18 additions & 3 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ react {
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
entryFile = file("../../index.ts")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
Expand All @@ -57,7 +57,7 @@ react {
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
def enableProguardInReleaseBuilds = true

/**
* The preferred build flavor of JavaScriptCore (JSC)
Expand All @@ -73,7 +73,7 @@ def enableProguardInReleaseBuilds = false
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'

android {
ndkVersion rootProject.ext.ndkVersion
// ndkVersion rootProject.ext.ndkVersion // Commented out - NDK version not defined in root build.gradle
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdk rootProject.ext.compileSdkVersion

Expand All @@ -92,6 +92,20 @@ android {
keyAlias 'androiddebugkey'
keyPassword 'android'
}
release {
def localPropertiesFile = rootProject.file("gradle-local.properties")
def localProperties = new Properties()
if (localPropertiesFile.exists()) {
localProperties.load(new FileInputStream(localPropertiesFile))
}

if (localProperties.getProperty('WMBR_UPLOAD_STORE_FILE')) {
storeFile file(localProperties.getProperty('WMBR_UPLOAD_STORE_FILE'))
storePassword localProperties.getProperty('WMBR_UPLOAD_STORE_PASSWORD')
keyAlias localProperties.getProperty('WMBR_UPLOAD_KEY_ALIAS')
keyPassword localProperties.getProperty('WMBR_UPLOAD_KEY_PASSWORD')
}
}
}
buildTypes {
debug {
Expand All @@ -101,6 +115,7 @@ android {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
signingConfig signingConfigs.release
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
Expand Down
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ buildscript {
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 35
ndkVersion = "27.1.12297006"
// ndkVersion = "27.1.12297006"
kotlinVersion = "2.1.20"
}
repositories {
Expand Down
9 changes: 9 additions & 0 deletions android/gradle-local.properties.default
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Android Signing Configuration Template
# Copy this file to gradle-local.properties and fill in your values
# gradle-local.properties is gitignored for security

# Release signing configuration for Google Play Store
WMBR_UPLOAD_STORE_FILE=wmbr-upload-key.keystore
WMBR_UPLOAD_STORE_PASSWORD=
WMBR_UPLOAD_KEY_ALIAS=wmbr-upload-key
WMBR_UPLOAD_KEY_PASSWORD=
4 changes: 2 additions & 2 deletions android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
# your application. You should enable this flag either if you want
# to write custom TurboModules/Fabric components OR use libraries that
# are providing them.
newArchEnabled=true
newArchEnabled=false

# Use this property to enable or disable the Hermes JS engine.
# If set to false, you will be using JSC instead.
hermesEnabled=true
hermesEnabled=true
Loading
Loading