Skip to content

Compass App: Add "server" dart shelf-app and "shared" dart package #2359

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

Merged
merged 14 commits into from
Jul 18, 2024
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
28 changes: 25 additions & 3 deletions compass_app/app/lib/config/dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,34 @@ import 'package:provider/provider.dart';

import '../data/repositories/continent/continent_repository.dart';
import '../data/repositories/continent/continent_repository_local.dart';
import '../data/repositories/continent/continent_repository_remote.dart';
import '../data/repositories/destination/destination_repository.dart';
import '../data/repositories/destination/destination_repository_local.dart';
import '../data/repositories/destination/destination_repository_remote.dart';
import '../data/services/api_client.dart';

/// Configure dependencies as a list of Providers
List<SingleChildWidget> get providers {
// List of Providers
/// Configure dependencies for remote data.
/// This dependency list uses repositories that connect to a remote server.
List<SingleChildWidget> get providersRemote {
final apiClient = ApiClient();

return [
Provider.value(
value: DestinationRepositoryRemote(
apiClient: apiClient,
) as DestinationRepository,
),
Provider.value(
value: ContinentRepositoryRemote(
apiClient: apiClient,
) as ContinentRepository,
),
];
}

/// Configure dependencies for local data.
/// This dependency list uses repositories that provide local data.
List<SingleChildWidget> get providersLocal {
return [
Provider.value(
value: DestinationRepositoryLocal() as DestinationRepository,
Expand Down
12 changes: 0 additions & 12 deletions compass_app/app/lib/data/models/continent.dart

This file was deleted.

50 changes: 0 additions & 50 deletions compass_app/app/lib/data/models/destination.dart

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:compass_model/model.dart';

import '../../../utils/result.dart';
import '../../models/continent.dart';

/// Data source with all possible continents.
abstract class ContinentRepository {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
import 'package:compass_model/model.dart';

import '../../../utils/result.dart';
import '../../models/continent.dart';
import 'continent_repository.dart';

/// Local data source with all possible regions.
/// Local data source with all possible continents.
class ContinentRepositoryLocal implements ContinentRepository {
@override
Future<Result<List<Continent>>> getContinents() {
return Future.value(
Result.ok(
[
Continent(
const Continent(
name: 'Europe',
imageUrl: 'https://rstr.in/google/tripedia/TmR12QdlVTT',
),
Continent(
const Continent(
name: 'Asia',
imageUrl: 'https://rstr.in/google/tripedia/VJ8BXlQg8O1',
),
Continent(
const Continent(
name: 'South America',
imageUrl: 'https://rstr.in/google/tripedia/flm_-o1aI8e',
),
Continent(
const Continent(
name: 'Africa',
imageUrl: 'https://rstr.in/google/tripedia/-nzi8yFOBpF',
),
Continent(
const Continent(
name: 'North America',
imageUrl: 'https://rstr.in/google/tripedia/jlbgFDrSUVE',
),
Continent(
const Continent(
name: 'Oceania',
imageUrl: 'https://rstr.in/google/tripedia/vxyrDE-fZVL',
),
Continent(
const Continent(
name: 'Australia',
imageUrl: 'https://rstr.in/google/tripedia/z6vy6HeRyvZ',
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:compass_model/model.dart';

import '../../../utils/result.dart';
import '../../services/api_client.dart';
import 'continent_repository.dart';

/// Remote data source for [Continent].
/// Implements basic local caching.
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
class ContinentRepositoryRemote implements ContinentRepository {
ContinentRepositoryRemote({
required ApiClient apiClient,
}) : _apiClient = apiClient;

final ApiClient _apiClient;

List<Continent>? _cachedData;

@override
Future<Result<List<Continent>>> getContinents() async {
if (_cachedData == null) {
// No cached data, request continents
final result = await _apiClient.getContinents();
if (result is Ok) {
// Store value if result Ok
_cachedData = result.asOk.value;
}
return result;
} else {
// Return cached data if available
return Result.ok(_cachedData!);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:compass_model/model.dart';

import '../../../utils/result.dart';
import '../../models/destination.dart';

/// Data source with all possible destinations
abstract class DestinationRepository {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'dart:convert';

import 'package:compass_model/model.dart';
import 'package:flutter/services.dart' show rootBundle;

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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import 'package:compass_model/model.dart';

import '../../../utils/result.dart';
import '../../services/api_client.dart';
import 'destination_repository.dart';

/// Remote data source for [Destination].
/// Implements basic local caching.
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
class DestinationRepositoryRemote implements DestinationRepository {
DestinationRepositoryRemote({
required ApiClient apiClient,
}) : _apiClient = apiClient;

final ApiClient _apiClient;

List<Destination>? _cachedData;

@override
Future<Result<List<Destination>>> getDestinations() async {
if (_cachedData == null) {
// No cached data, request destinations
final result = await _apiClient.getDestinations();
if (result is Ok) {
// Store value if result Ok
_cachedData = result.asOk.value;
}
return result;
} else {
// Return cached data if available
return Result.ok(_cachedData!);
}
}
}
49 changes: 49 additions & 0 deletions compass_app/app/lib/data/services/api_client.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'dart:convert';
import 'dart:io';
import 'package:compass_model/model.dart';

import '../../utils/result.dart';

// TODO: Basic auth request
// TODO: Configurable baseurl/host/port
class ApiClient {
Future<Result<List<Continent>>> getContinents() async {
final client = HttpClient();
try {
final request = await client.get('localhost', 8080, '/continent');
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
final json = jsonDecode(stringData) as List<dynamic>;
return Result.ok(
json.map((element) => Continent.fromJson(element)).toList());
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}

Future<Result<List<Destination>>> getDestinations() async {
final client = HttpClient();
try {
final request = await client.get('localhost', 8080, '/destination');
final response = await request.close();
if (response.statusCode == 200) {
final stringData = await response.transform(utf8.decoder).join();
final json = jsonDecode(stringData) as List<dynamic>;
return Result.ok(
json.map((element) => Destination.fromJson(element)).toList());
} else {
return Result.error(const HttpException("Invalid response"));
}
} on Exception catch (error) {
return Result.error(error);
} finally {
client.close();
}
}
}
14 changes: 4 additions & 10 deletions compass_app/app/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,20 +1,14 @@
import 'config/dependencies.dart';
import 'ui/core/themes/theme.dart';
import 'routing/router.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'ui/core/ui/scroll_behavior.dart';
import 'main_development.dart' as development;

/// Default main method
void main() {
runApp(
MultiProvider(
// Loading the default providers
// NOTE: We can load different configurations e.g. fakes
providers: providers,
child: const MainApp(),
),
);
// Launch development config by default
development.main();
}

class MainApp extends StatelessWidget {
Expand Down
17 changes: 17 additions & 0 deletions compass_app/app/lib/main_development.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'config/dependencies.dart';
import 'main.dart';

/// Development config entry point.
/// Launch with `flutter run --target lib/main_development.dart`.
/// Uses local data.
void main() {
runApp(
MultiProvider(
providers: providersLocal,
child: const MainApp(),
),
);
}
17 changes: 17 additions & 0 deletions compass_app/app/lib/main_staging.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'config/dependencies.dart';
import 'main.dart';

/// Staging config entry point.
/// Launch with `flutter run --target lib/main_staging.dart`.
/// Uses remote data from a server.
void main() {
runApp(
MultiProvider(
providers: providersRemote,
child: const MainApp(),
),
);
}
3 changes: 2 additions & 1 deletion compass_app/app/lib/routing/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ final router = GoRouter(
GoRoute(
path: '/',
builder: (context, state) {
final viewModel = SearchFormViewModel(continentRepository: context.read());
final viewModel =
SearchFormViewModel(continentRepository: context.read());
return SearchFormScreen(viewModel: viewModel);
},
routes: [
Expand Down
3 changes: 1 addition & 2 deletions compass_app/app/lib/ui/core/themes/colors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ class AppColors {
static const grey3 = Color(0xFFA4A4A4);
static const whiteTransparent =
Color(0x4DFFFFFF); // Figma rgba(255, 255, 255, 0.3)
static const blackTransparent =
Color(0x4D000000);
static const blackTransparent = Color(0x4D000000);

static const lightColorScheme = ColorScheme(
brightness: Brightness.light,
Expand Down
8 changes: 4 additions & 4 deletions compass_app/app/lib/ui/core/ui/scroll_behavior.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
class AppCustomScrollBehavior extends MaterialScrollBehavior {
@override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
// Allow to drag with mouse on Regions carousel
PointerDeviceKind.mouse,
};
PointerDeviceKind.touch,
// Allow to drag with mouse on Regions carousel
PointerDeviceKind.mouse,
};
}
Loading