feat: add Flutter flavors for dev/prod builds and update app icon

- Add Android productFlavors (dev: social.coves.dev, prod: social.coves)
- Create iOS flavor xcconfig files for future scheme setup
- Update EnvironmentConfig to support flavor-based environment detection
- Add VSCode launch configurations for easy flavor switching
- Update app icon to lil_dude mascot with proper adaptive icon padding

Dev flavor points to local server, prod flavor points to coves.social.
Both apps can be installed side-by-side on the same device.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

+59
.vscode/launch.json
···
+
{
+
"version": "0.2.0",
+
"configurations": [
+
{
+
"name": "Dev (Local Server)",
+
"request": "launch",
+
"type": "dart",
+
"flutterMode": "debug",
+
"args": [
+
"--flavor",
+
"dev",
+
"--dart-define=FLUTTER_FLAVOR=dev"
+
]
+
},
+
{
+
"name": "Dev (Local Server) - Release",
+
"request": "launch",
+
"type": "dart",
+
"flutterMode": "release",
+
"args": [
+
"--flavor",
+
"dev",
+
"--dart-define=FLUTTER_FLAVOR=dev"
+
]
+
},
+
{
+
"name": "Prod (Production Server)",
+
"request": "launch",
+
"type": "dart",
+
"flutterMode": "debug",
+
"args": [
+
"--flavor",
+
"prod",
+
"--dart-define=FLUTTER_FLAVOR=prod"
+
]
+
},
+
{
+
"name": "Prod (Production Server) - Release",
+
"request": "launch",
+
"type": "dart",
+
"flutterMode": "release",
+
"args": [
+
"--flavor",
+
"prod",
+
"--dart-define=FLUTTER_FLAVOR=prod"
+
]
+
},
+
{
+
"name": "Legacy: Local (no flavor)",
+
"request": "launch",
+
"type": "dart",
+
"flutterMode": "debug",
+
"args": [
+
"--dart-define=ENVIRONMENT=local"
+
]
+
}
+
],
+
"compounds": []
+
}
+16 -1
android/app/build.gradle.kts
···
}
defaultConfig {
-
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+
// Base application ID - flavors will add suffixes
applicationId = "social.coves"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
···
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
+
}
+
+
// Flutter flavors for side-by-side installation
+
flavorDimensions += "environment"
+
productFlavors {
+
create("prod") {
+
dimension = "environment"
+
applicationIdSuffix = ""
+
resValue("string", "app_name", "Coves")
+
}
+
create("dev") {
+
dimension = "environment"
+
applicationIdSuffix = ".dev"
+
resValue("string", "app_name", "Coves Dev")
+
}
}
buildTypes {
+1 -1
android/app/src/main/AndroidManifest.xml
···
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
-
android:label="Coves"
+
android:label="@string/app_name"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-hdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-mdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-xhdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png

This is a binary file and will not be displayed.

android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png

This is a binary file and will not be displayed.

assets/logo/lil_dude.png

This is a binary file and will not be displayed.

assets/logo/lil_dude_padded.png

This is a binary file and will not be displayed.

+8
ios/Flutter/Dev-Debug.xcconfig
···
+
#include "Generated.xcconfig"
+
#include "Debug.xcconfig"
+
+
// Dev flavor configuration
+
PRODUCT_BUNDLE_IDENTIFIER=social.coves.dev
+
PRODUCT_NAME=Coves Dev
+
DISPLAY_NAME=Coves Dev
+
FLUTTER_FLAVOR=dev
+8
ios/Flutter/Dev-Release.xcconfig
···
+
#include "Generated.xcconfig"
+
#include "Release.xcconfig"
+
+
// Dev flavor configuration
+
PRODUCT_BUNDLE_IDENTIFIER=social.coves.dev
+
PRODUCT_NAME=Coves Dev
+
DISPLAY_NAME=Coves Dev
+
FLUTTER_FLAVOR=dev
+8
ios/Flutter/Prod-Debug.xcconfig
···
+
#include "Generated.xcconfig"
+
#include "Debug.xcconfig"
+
+
// Prod flavor configuration
+
PRODUCT_BUNDLE_IDENTIFIER=social.coves
+
PRODUCT_NAME=Coves
+
DISPLAY_NAME=Coves
+
FLUTTER_FLAVOR=prod
+8
ios/Flutter/Prod-Release.xcconfig
···
+
#include "Generated.xcconfig"
+
#include "Release.xcconfig"
+
+
// Prod flavor configuration
+
PRODUCT_BUNDLE_IDENTIFIER=social.coves
+
PRODUCT_NAME=Coves
+
DISPLAY_NAME=Coves
+
FLUTTER_FLAVOR=prod
ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png

This is a binary file and will not be displayed.

ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png

This is a binary file and will not be displayed.

+2 -2
ios/Runner/Info.plist
···
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
-
<string>Coves</string>
+
<string>$(DISPLAY_NAME)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
···
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
-
<string>coves_flutter</string>
+
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
+47 -14
lib/config/environment_config.dart
···
/// Environment Configuration for Coves Mobile
///
/// Supports multiple environments:
-
/// - Production: Real Bluesky infrastructure
-
/// - Local: Local PDS + PLC for development/testing
+
/// - Production: Real Bluesky infrastructure (prod flavor)
+
/// - Local: Local PDS + PLC for development/testing (dev flavor)
///
-
/// Set via ENVIRONMENT environment variable or flutter run --dart-define
+
/// Environment is determined by (in priority order):
+
/// 1. --dart-define=ENVIRONMENT=local/production (explicit override)
+
/// 2. Flutter flavor (dev -> local, prod -> production)
+
/// 3. Default: production
enum Environment { production, local }
class EnvironmentConfig {
···
final String plcDirectoryUrl;
/// Production configuration (default)
-
/// Uses real Bluesky infrastructure
+
/// Uses Coves production server with public atproto infrastructure
static const production = EnvironmentConfig(
environment: Environment.production,
-
apiUrl: 'https://coves.social', // TODO: Update when production is live
+
apiUrl: 'https://coves.social',
handleResolverUrl:
'https://bsky.social/xrpc/com.atproto.identity.resolveHandle',
plcDirectoryUrl: 'https://plc.directory',
···
plcDirectoryUrl: 'http://localhost:3002',
);
+
/// Flutter flavor passed via --flavor flag
+
/// This is set automatically by Flutter build system
+
static const String _flavor = String.fromEnvironment('FLUTTER_FLAVOR');
+
+
/// Explicit environment override via --dart-define=ENVIRONMENT=local
+
static const String _envOverride = String.fromEnvironment('ENVIRONMENT');
+
/// Get current environment based on build configuration
+
///
+
/// Priority:
+
/// 1. Explicit --dart-define=ENVIRONMENT=local/production
+
/// 2. Flavor: dev -> local, prod -> production
+
/// 3. Default: production
static EnvironmentConfig get current {
-
// Read from --dart-define=ENVIRONMENT=local
-
const envString = String.fromEnvironment(
-
'ENVIRONMENT',
-
defaultValue: 'production',
-
);
+
// Priority 1: Explicit environment override
+
if (_envOverride.isNotEmpty) {
+
switch (_envOverride) {
+
case 'local':
+
return local;
+
case 'production':
+
return production;
+
}
+
}
-
switch (envString) {
-
case 'local':
+
// Priority 2: Flavor-based environment
+
switch (_flavor) {
+
case 'dev':
return local;
-
case 'production':
-
default:
+
case 'prod':
return production;
}
+
+
// Default: production
+
return production;
+
}
+
+
/// Get the current flavor name for display purposes
+
static String get flavorName {
+
if (_flavor.isNotEmpty) {
+
return _flavor;
+
}
+
if (_envOverride == 'local') {
+
return 'dev';
+
}
+
return 'prod';
}
bool get isProduction => environment == Environment.production;
+2 -2
pubspec.yaml
···
flutter_launcher_icons:
android: true
ios: true
-
image_path: "assets/logo/app-icon.png"
+
image_path: "assets/logo/lil_dude_padded.png"
adaptive_icon_background: "#0B0F14" # Your app's dark background color
-
adaptive_icon_foreground: "assets/logo/app-icon-foreground.png" # Smaller version with padding
+
adaptive_icon_foreground: "assets/logo/lil_dude_padded.png" # Lil dude with safe zone padding
remove_alpha_ios: true # Required for App Store submission
# For information on the generic Dart part of this file, see the