feat(meezi_app): Meezi green theme + rich discovery API (Koja parity, code-only)
CI/CD / CI · API (dotnet build + test) (push) Successful in 42s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 37s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 38s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 50s
CI/CD / Deploy · all services (push) Successful in 23s
CI/CD / CI · API (dotnet build + test) (push) Successful in 42s
CI/CD / CI · Admin API (dotnet build) (push) Successful in 37s
CI/CD / CI · Dashboard (tsc) (push) Successful in 1m5s
CI/CD / CI · Admin Web (tsc) (push) Successful in 38s
CI/CD / CI · Website (tsc) (push) Successful in 45s
CI/CD / CI · Koja (tsc) (push) Successful in 50s
CI/CD / Deploy · all services (push) Successful in 23s
Head-start on the Koja-Flutter build while pub access is unavailable (pub.dev 403 under sanctions). NOT yet built/verified — needs `flutter create` + `pub get` once package access is restored. - core/theme/app_theme.dart: centralized MeeziTheme (brand green #0F6E56, Material 3, filled/outlined buttons, inputs), wired into main.dart (was a brown seed, no theme). - public_api.dart: discover() gains the full filter set (themes/vibes/occasions/ spaceFeatures/noise/priceTier/size/openNow) + discoverNearby/nlpParse/discoverTaxonomy, matching the web Koja's backend surface. Follows the existing dio pattern.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Meezi brand palette. Green #0F6E56 matches the dashboard / Koja web.
|
||||
class MeeziColors {
|
||||
static const Color brand = Color(0xFF0F6E56);
|
||||
static const Color brandDark = Color(0xFF0B5544);
|
||||
static const Color accent = Color(0xFFE1F5EE);
|
||||
static const Color surface = Color(0xFFF9FAFB);
|
||||
}
|
||||
|
||||
/// Centralized Meezi theme. Uses Vazirmatn when the font is bundled (see pubspec);
|
||||
/// falls back to the platform font otherwise. Kept to stable Material 3 APIs.
|
||||
class MeeziTheme {
|
||||
static ThemeData light() {
|
||||
final scheme = ColorScheme.fromSeed(
|
||||
seedColor: MeeziColors.brand,
|
||||
primary: MeeziColors.brand,
|
||||
brightness: Brightness.light,
|
||||
);
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: scheme,
|
||||
fontFamily: 'Vazirmatn',
|
||||
scaffoldBackgroundColor: MeeziColors.surface,
|
||||
appBarTheme: const AppBarTheme(
|
||||
elevation: 0,
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black87,
|
||||
),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: MeeziColors.brand,
|
||||
foregroundColor: Colors.white,
|
||||
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 18),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: MeeziColors.brand,
|
||||
side: const BorderSide(color: MeeziColors.brand),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: Colors.white,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.black.withValues(alpha: 0.10)),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide(color: Colors.black.withValues(alpha: 0.10)),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(color: MeeziColors.brand, width: 1.5),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static ThemeData dark() {
|
||||
final scheme = ColorScheme.fromSeed(
|
||||
seedColor: MeeziColors.brand,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
return ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: scheme,
|
||||
fontFamily: 'Vazirmatn',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,12 +10,28 @@ class PublicApi {
|
||||
String? q,
|
||||
double? minRating,
|
||||
String? sort,
|
||||
List<String>? themes,
|
||||
List<String>? vibes,
|
||||
List<String>? occasions,
|
||||
List<String>? spaceFeatures,
|
||||
String? noise,
|
||||
String? priceTier,
|
||||
String? size,
|
||||
bool openNow = false,
|
||||
}) async {
|
||||
final params = <String, String>{};
|
||||
if (city != null && city.isNotEmpty) params['city'] = city;
|
||||
if (q != null && q.isNotEmpty) params['q'] = q;
|
||||
if (minRating != null) params['minRating'] = minRating.toString();
|
||||
if (sort != null && sort.isNotEmpty) params['sort'] = sort;
|
||||
if (themes != null && themes.isNotEmpty) params['themes'] = themes.join(',');
|
||||
if (vibes != null && vibes.isNotEmpty) params['vibes'] = vibes.join(',');
|
||||
if (occasions != null && occasions.isNotEmpty) params['occasions'] = occasions.join(',');
|
||||
if (spaceFeatures != null && spaceFeatures.isNotEmpty) params['spaceFeatures'] = spaceFeatures.join(',');
|
||||
if (noise != null && noise.isNotEmpty) params['noise'] = noise;
|
||||
if (priceTier != null && priceTier.isNotEmpty) params['priceTier'] = priceTier;
|
||||
if (size != null && size.isNotEmpty) params['size'] = size;
|
||||
if (openNow) params['openNow'] = 'true';
|
||||
final res = await _client.dio.get<Map<String, dynamic>>(
|
||||
'/api/public/discover',
|
||||
queryParameters: params.isEmpty ? null : params,
|
||||
@@ -24,6 +40,43 @@ class PublicApi {
|
||||
return list.cast<Map<String, dynamic>>();
|
||||
}
|
||||
|
||||
/// Cafés near a coordinate, sorted by distance (for "near me").
|
||||
Future<List<Map<String, dynamic>>> discoverNearby({
|
||||
required double lat,
|
||||
required double lng,
|
||||
String? excludeSlug,
|
||||
int limit = 12,
|
||||
}) async {
|
||||
final res = await _client.dio.get<Map<String, dynamic>>(
|
||||
'/api/public/discover/near',
|
||||
queryParameters: {
|
||||
'lat': lat,
|
||||
'lng': lng,
|
||||
if (excludeSlug != null && excludeSlug.isNotEmpty) 'excludeSlug': excludeSlug,
|
||||
'limit': limit,
|
||||
},
|
||||
);
|
||||
final list = res.data?['data'] as List<dynamic>? ?? [];
|
||||
return list.cast<Map<String, dynamic>>();
|
||||
}
|
||||
|
||||
/// Parse a free-text query into structured discovery hints (themes/vibes/...).
|
||||
Future<Map<String, dynamic>?> nlpParse(String q) async {
|
||||
final res = await _client.dio.get<Map<String, dynamic>>(
|
||||
'/api/public/discover/nlp-parse',
|
||||
queryParameters: {'q': q},
|
||||
);
|
||||
return res.data?['data'] as Map<String, dynamic>?;
|
||||
}
|
||||
|
||||
/// The discovery taxonomy (available themes, vibes, occasions, space features).
|
||||
Future<Map<String, dynamic>?> discoverTaxonomy() async {
|
||||
final res = await _client.dio.get<Map<String, dynamic>>(
|
||||
'/api/public/discover-profile/taxonomy',
|
||||
);
|
||||
return res.data?['data'] as Map<String, dynamic>?;
|
||||
}
|
||||
|
||||
Future<List<Map<String, dynamic>>> getReviews(String slug, {int page = 1}) async {
|
||||
final res = await _client.dio.get<Map<String, dynamic>>(
|
||||
'/api/public/cafes/$slug/reviews',
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'app/router.dart';
|
||||
import 'core/theme/app_theme.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const ProviderScope(child: MeeziApp()));
|
||||
@@ -22,10 +23,9 @@ class MeeziApp extends StatelessWidget {
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
],
|
||||
theme: ThemeData(
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF6B4F3A)),
|
||||
useMaterial3: true,
|
||||
),
|
||||
theme: MeeziTheme.light(),
|
||||
darkTheme: MeeziTheme.dark(),
|
||||
themeMode: ThemeMode.light,
|
||||
routerConfig: appRouter,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user