-
Notifications
You must be signed in to change notification settings - Fork 7.7k
Create compass-app first feature #2342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
56ff720
f58fd1c
92e5de8
ce60385
92af5b6
c97b83a
431177e
df928c6
e978f4d
16fa89a
c343c82
9885024
a583b31
c6ccc23
717ab93
9736a53
aefd0d8
ef5a639
9a3940b
f5e4c94
a2bd371
cc7346a
d04c8e3
bb102a8
813ded4
ec70627
3b1bd6c
fbd136e
f63e93c
ed0da99
7319afd
51b68a3
5f1f3e5
dc9aa3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
include: package:flutter_lints/flutter.yaml | ||
|
||
linter: | ||
rules: | ||
- prefer_relative_imports |
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
description: This file stores settings for Dart & Flutter DevTools. | ||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states | ||
extensions: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import '../data/repositories/destination/destination_repository.dart'; | ||
import '../data/repositories/destination/destination_repository_local.dart'; | ||
import 'package:provider/single_child_widget.dart'; | ||
import 'package:provider/provider.dart'; | ||
|
||
/// Configure dependencies as a list of Providers | ||
List<SingleChildWidget> get providers { | ||
// List of Providers | ||
return [ | ||
Provider.value( | ||
value: DestinationRepositoryLocal() as DestinationRepository, | ||
), | ||
]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/// Model class for Destination data | ||
class Destination { | ||
Destination({ | ||
required this.ref, | ||
required this.name, | ||
required this.country, | ||
required this.continent, | ||
required this.knownFor, | ||
required this.tags, | ||
required this.imageUrl, | ||
}); | ||
|
||
/// e.g. 'alaska' | ||
final String ref; | ||
|
||
/// e.g. 'Alaska' | ||
final String name; | ||
|
||
/// e.g. 'United States' | ||
final String country; | ||
|
||
/// e.g. 'North America' | ||
final String continent; | ||
|
||
/// e.g. 'Alaska is a haven for outdoor enthusiasts ...' | ||
final String knownFor; | ||
|
||
/// e.g. ['Mountain', 'Off-the-beaten-path', 'Wildlife watching'] | ||
final List<String> tags; | ||
|
||
/// e.g. 'https://storage.googleapis.com/tripedia-images/destinations/alaska.jpg' | ||
final String imageUrl; | ||
|
||
@override | ||
String toString() { | ||
return 'Destination{ref: $ref, name: $name, country: $country, continent: $continent, knownFor: $knownFor, tags: $tags, imageUrl: $imageUrl}'; | ||
} | ||
|
||
factory Destination.fromJson(Map<String, dynamic> json) { | ||
return Destination( | ||
ref: json['ref'] as String, | ||
name: json['name'] as String, | ||
country: json['country'] as String, | ||
continent: json['continent'] as String, | ||
knownFor: json['knownFor'] as String, | ||
tags: (json['tags'] as List<dynamic>).map((e) => e as String).toList(), | ||
imageUrl: json['imageUrl'] as String, | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import '../../../utils/result.dart'; | ||
import '../../models/destination.dart'; | ||
|
||
/// Data source with all possible destinations | ||
abstract class DestinationRepository { | ||
/// Get complete list of destinations | ||
Future<Result<List<Destination>>> getDestinations(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import 'dart:convert'; | ||
|
||
import '../../../utils/result.dart'; | ||
import '../../models/destination.dart'; | ||
import 'destination_repository.dart'; | ||
|
||
import 'package:flutter/services.dart' show rootBundle; | ||
|
||
/// Local implementation of DestinationRepository | ||
/// Uses data from assets folder | ||
class DestinationRepositoryLocal implements DestinationRepository { | ||
/// Obtain list of destinations from local assets | ||
@override | ||
Future<Result<List<Destination>>> getDestinations() async { | ||
try { | ||
final localData = await _loadAsset(); | ||
final list = _parse(localData); | ||
return Result.ok(list); | ||
} on Exception catch (error) { | ||
return Result.error(error); | ||
} | ||
} | ||
|
||
Future<String> _loadAsset() async { | ||
return await rootBundle.loadString('assets/destinations.json'); | ||
} | ||
|
||
List<Destination> _parse(String localData) { | ||
final parsed = (jsonDecode(localData) as List).cast<Map<String, dynamic>>(); | ||
|
||
return parsed | ||
.map<Destination>((json) => Destination.fromJson(json)) | ||
.toList(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import 'config/dependencies.dart'; | ||
import 'ui/core/themes/theme.dart'; | ||
import 'routing/router.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:provider/provider.dart'; | ||
|
||
void main() { | ||
runApp( | ||
MultiProvider( | ||
// Loading the default providers | ||
// NOTE: We can load different configurations e.g. fakes | ||
providers: providers, | ||
child: const MainApp(), | ||
), | ||
); | ||
} | ||
|
||
class MainApp extends StatelessWidget { | ||
const MainApp({super.key}); | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return MaterialApp.router( | ||
theme: AppTheme.lightTheme, | ||
darkTheme: AppTheme.darkTheme, | ||
themeMode: ThemeMode.system, | ||
routerConfig: router, | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import '../ui/results/widgets/results_screen.dart'; | ||
import 'package:go_router/go_router.dart'; | ||
import 'package:provider/provider.dart'; | ||
|
||
import '../ui/results/view_models/results_viewmodel.dart'; | ||
|
||
/// Top go_router entry point | ||
final router = GoRouter( | ||
initialLocation: '/results', | ||
routes: [ | ||
GoRoute( | ||
path: '/results', | ||
builder: (context, state) { | ||
final viewModel = ResultsViewModel( | ||
destinationRepository: context.read(), | ||
); | ||
return ResultsScreen(viewModel: viewModel); | ||
}, | ||
), | ||
], | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import 'package:flutter/material.dart'; | ||
|
||
class AppColors { | ||
static const black1 = Color(0xFF101010); | ||
static const white1 = Color(0xFFFFF7FA); | ||
static const grey1 = Color(0xFFF2F2F2); | ||
static const grey2 = Color(0xFF4D4D4D); | ||
static const whiteTransparent = | ||
Color(0x4DFFFFFF); // Figma rgba(255, 255, 255, 0.3) | ||
static const blackTransparent = | ||
Color(0x4D000000); // Figma rgba(255, 255, 255, 0.3) | ||
|
||
static const lightColorScheme = ColorScheme( | ||
brightness: Brightness.light, | ||
primary: AppColors.black1, | ||
onPrimary: AppColors.white1, | ||
secondary: AppColors.black1, | ||
onSecondary: AppColors.white1, | ||
surface: Colors.white, | ||
onSurface: AppColors.black1, | ||
error: Colors.red, | ||
onError: Colors.white, | ||
); | ||
|
||
static const darkColorScheme = ColorScheme( | ||
brightness: Brightness.dark, | ||
primary: AppColors.white1, | ||
onPrimary: AppColors.black1, | ||
secondary: AppColors.white1, | ||
onSecondary: AppColors.black1, | ||
surface: AppColors.black1, | ||
onSurface: Colors.white, | ||
error: Colors.red, | ||
onError: Colors.white, | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:google_fonts/google_fonts.dart'; | ||
|
||
// TODO: Maybe the text styles here should be moved to the respective widgets | ||
class TextStyles { | ||
// Note: original Figma file uses Nikkei Maru | ||
// which is not available on GoogleFonts | ||
// Note: Card title theme doesn't change based on light/dark mode | ||
static final cardTitleStyle = GoogleFonts.rubik( | ||
textStyle: const TextStyle( | ||
fontWeight: FontWeight.w800, | ||
fontSize: 15.0, | ||
color: Colors.white, | ||
miquelbeltran marked this conversation as resolved.
Show resolved
Hide resolved
|
||
letterSpacing: 1, | ||
shadows: [ | ||
// Helps to read the text a bit better | ||
Shadow( | ||
blurRadius: 3.0, | ||
color: Colors.black, | ||
) | ||
], | ||
), | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import 'colors.dart'; | ||
import '../ui/tag_chip.dart'; | ||
import 'package:flutter/material.dart'; | ||
|
||
class AppTheme { | ||
static ThemeData lightTheme = ThemeData( | ||
useMaterial3: true, | ||
colorScheme: AppColors.lightColorScheme, | ||
extensions: [ | ||
TagChipTheme( | ||
chipColor: AppColors.whiteTransparent, | ||
onChipColor: Colors.white, | ||
), | ||
], | ||
); | ||
|
||
static ThemeData darkTheme = ThemeData( | ||
useMaterial3: true, | ||
colorScheme: AppColors.darkColorScheme, | ||
extensions: [ | ||
TagChipTheme( | ||
chipColor: AppColors.blackTransparent, | ||
onChipColor: Colors.white, | ||
), | ||
], | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import 'dart:ui'; | ||
|
||
import '../themes/colors.dart'; | ||
import 'package:flutter/material.dart'; | ||
import 'package:google_fonts/google_fonts.dart'; | ||
|
||
class TagChip extends StatelessWidget { | ||
const TagChip({ | ||
super.key, | ||
required this.tag, | ||
}); | ||
|
||
final String tag; | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
return ClipRRect( | ||
borderRadius: BorderRadius.circular(10.0), | ||
child: BackdropFilter( | ||
filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), | ||
child: DecoratedBox( | ||
decoration: BoxDecoration( | ||
color: Theme.of(context).extension<TagChipTheme>()?.chipColor ?? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh this would be an excellent spot to demonstrate good theming defaults best practices! It's common in many of the Flutter material widgets, like here: Basically, there's an order of priority of where themed elements of a widget come from. They're used in this order:
If you added a This pattern is really great as it gives devs flexibility to theme one-off widgets easily, while still having an app-wide theme to fall back on. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea! I have copied your comment into our internal doc to comment on this when we talk about widget design |
||
AppColors.whiteTransparent, | ||
), | ||
child: SizedBox( | ||
height: 20.0, | ||
child: Padding( | ||
padding: const EdgeInsets.symmetric(horizontal: 6.0), | ||
child: Row( | ||
crossAxisAlignment: CrossAxisAlignment.center, | ||
mainAxisSize: MainAxisSize.min, | ||
children: [ | ||
Icon( | ||
_iconFrom(tag), | ||
color: Theme.of(context) | ||
.extension<TagChipTheme>() | ||
?.onChipColor, | ||
size: 10, | ||
), | ||
const SizedBox(width: 4), | ||
Text( | ||
tag, | ||
textAlign: TextAlign.center, | ||
style: _textStyle(context), | ||
), | ||
], | ||
), | ||
), | ||
), | ||
), | ||
), | ||
); | ||
} | ||
|
||
IconData? _iconFrom(String tag) { | ||
return switch (tag) { | ||
'Adventure sports' => Icons.kayaking_outlined, | ||
'Beach' => Icons.beach_access_outlined, | ||
'City' => Icons.location_city_outlined, | ||
'Cultural experiences' => Icons.museum_outlined, | ||
'Foodie' || 'Food tours' => Icons.restaurant, | ||
'Hiking' => Icons.hiking, | ||
'Historic' => Icons.menu_book_outlined, | ||
'Island' || 'Coastal' || 'Lake' || 'River' => Icons.water, | ||
'Luxury' => Icons.attach_money_outlined, | ||
'Mountain' || 'Wildlife watching' => Icons.landscape_outlined, | ||
'Nightlife' => Icons.local_bar_outlined, | ||
'Off-the-beaten-path' => Icons.do_not_step_outlined, | ||
'Romantic' => Icons.favorite_border_outlined, | ||
'Rural' => Icons.agriculture_outlined, | ||
'Secluded' => Icons.church_outlined, | ||
'Sightseeing' => Icons.attractions_outlined, | ||
'Skiing' => Icons.downhill_skiing_outlined, | ||
'Wine tasting' => Icons.wine_bar_outlined, | ||
'Winter destination' => Icons.ac_unit, | ||
_ => Icons.label_outlined, | ||
}; | ||
} | ||
|
||
// Note: original Figma file uses Google Sans | ||
// which is not available on GoogleFonts | ||
_textStyle(BuildContext context) => GoogleFonts.openSans( | ||
textStyle: TextStyle( | ||
fontWeight: FontWeight.w500, | ||
fontSize: 10, | ||
color: Theme.of(context).extension<TagChipTheme>()?.onChipColor ?? | ||
Colors.white, | ||
textBaseline: TextBaseline.alphabetic, | ||
), | ||
); | ||
} | ||
|
||
class TagChipTheme extends ThemeExtension<TagChipTheme> { | ||
final Color chipColor; | ||
final Color onChipColor; | ||
|
||
TagChipTheme({ | ||
required this.chipColor, | ||
required this.onChipColor, | ||
}); | ||
|
||
@override | ||
ThemeExtension<TagChipTheme> copyWith({ | ||
Color? chipColor, | ||
Color? onChipColor, | ||
}) { | ||
return TagChipTheme( | ||
chipColor: chipColor ?? this.chipColor, | ||
onChipColor: onChipColor ?? this.onChipColor, | ||
); | ||
} | ||
|
||
@override | ||
ThemeExtension<TagChipTheme> lerp( | ||
covariant ThemeExtension<TagChipTheme> other, | ||
double t, | ||
) { | ||
if (other is! TagChipTheme) { | ||
return this; | ||
} | ||
return TagChipTheme( | ||
chipColor: Color.lerp(chipColor, other.chipColor, t) ?? chipColor, | ||
onChipColor: Color.lerp(onChipColor, other.onChipColor, t) ?? onChipColor, | ||
); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.