Skip to content

Commit 536c8e8

Browse files
committed
Merge branch 'compass-app' of https://github.com/flutter/samples into compass-app
2 parents a1ed3be + e0f25da commit 536c8e8

File tree

47 files changed

+1607
-89
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1607
-89
lines changed

compass_app/app/assets/logo.svg

Lines changed: 10 additions & 0 deletions
Loading

compass_app/app/lib/config/dependencies.dart

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import 'package:provider/single_child_widget.dart';
22
import 'package:provider/provider.dart';
33

4+
import '../domain/components/auth/auth_login_component.dart';
5+
import '../domain/components/auth/auth_logout_component.dart';
46
import '../data/repositories/activity/activity_repository.dart';
57
import '../data/repositories/activity/activity_repository_local.dart';
68
import '../data/repositories/activity/activity_repository_remote.dart';
9+
import '../data/repositories/auth/auth_token_repository.dart';
10+
import '../data/repositories/auth/auth_token_repository_dev.dart';
11+
import '../data/repositories/auth/auth_token_repository_shared_prefs.dart';
712
import '../data/repositories/continent/continent_repository.dart';
813
import '../data/repositories/continent/continent_repository_local.dart';
914
import '../data/repositories/continent/continent_repository_remote.dart';
@@ -13,8 +18,8 @@ import '../data/repositories/destination/destination_repository_remote.dart';
1318
import '../data/repositories/itinerary_config/itinerary_config_repository.dart';
1419
import '../data/repositories/itinerary_config/itinerary_config_repository_memory.dart';
1520
import '../data/services/api_client.dart';
16-
import '../ui/booking/components/booking_create_component.dart';
17-
import '../ui/booking/components/booking_share_component.dart';
21+
import '../domain/components/booking/booking_create_component.dart';
22+
import '../domain/components/booking/booking_share_component.dart';
1823

1924
/// Shared providers for all configurations.
2025
List<SingleChildWidget> _sharedProviders = [
@@ -29,27 +34,44 @@ List<SingleChildWidget> _sharedProviders = [
2934
lazy: true,
3035
create: (context) => BookingShareComponent.withSharePlus(),
3136
),
37+
Provider(
38+
lazy: true,
39+
create: (context) => AuthLogoutComponent(
40+
authTokenRepository: context.read(),
41+
itineraryConfigRepository: context.read(),
42+
),
43+
),
3244
];
3345

3446
/// Configure dependencies for remote data.
3547
/// This dependency list uses repositories that connect to a remote server.
3648
List<SingleChildWidget> get providersRemote {
37-
final apiClient = ApiClient();
38-
3949
return [
40-
Provider.value(
41-
value: DestinationRepositoryRemote(
42-
apiClient: apiClient,
50+
ChangeNotifierProvider.value(
51+
value: AuthTokenRepositorySharedPrefs() as AuthTokenRepository,
52+
),
53+
Provider(
54+
create: (context) => ApiClient(authTokenRepository: context.read()),
55+
),
56+
Provider(
57+
create: (context) => AuthLoginComponent(
58+
authTokenRepository: context.read(),
59+
apiClient: context.read(),
60+
),
61+
),
62+
Provider(
63+
create: (context) => DestinationRepositoryRemote(
64+
apiClient: context.read(),
4365
) as DestinationRepository,
4466
),
45-
Provider.value(
46-
value: ContinentRepositoryRemote(
47-
apiClient: apiClient,
67+
Provider(
68+
create: (context) => ContinentRepositoryRemote(
69+
apiClient: context.read(),
4870
) as ContinentRepository,
4971
),
50-
Provider.value(
51-
value: ActivityRepositoryRemote(
52-
apiClient: apiClient,
72+
Provider(
73+
create: (context) => ActivityRepositoryRemote(
74+
apiClient: context.read(),
5375
) as ActivityRepository,
5476
),
5577
Provider.value(
@@ -61,8 +83,12 @@ List<SingleChildWidget> get providersRemote {
6183

6284
/// Configure dependencies for local data.
6385
/// This dependency list uses repositories that provide local data.
86+
/// The user is always logged in.
6487
List<SingleChildWidget> get providersLocal {
6588
return [
89+
ChangeNotifierProvider.value(
90+
value: AuthTokenRepositoryDev() as AuthTokenRepository,
91+
),
6692
Provider.value(
6793
value: DestinationRepositoryLocal() as DestinationRepository,
6894
),
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'package:flutter/foundation.dart';
2+
3+
import '../../../utils/result.dart';
4+
5+
/// Repository to save and get auth token.
6+
/// Notifies listeners when the token changes e.g. user logs out.
7+
abstract class AuthTokenRepository extends ChangeNotifier {
8+
/// Get the token.
9+
/// If the value is null, usually means that the user is logged out.
10+
Future<Result<String?>> getToken();
11+
12+
/// Store the token.
13+
/// Will notifiy listeners.
14+
Future<Result<void>> saveToken(String? token);
15+
16+
/// Returns true when the token exists, otherwise false.
17+
Future<bool> hasToken() async {
18+
final result = await getToken();
19+
return result is Ok<String?> && result.value != null;
20+
}
21+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import '../../../utils/result.dart';
2+
import 'auth_token_repository.dart';
3+
4+
/// Development [AuthTokenRepository] that always returns a fake token
5+
class AuthTokenRepositoryDev extends AuthTokenRepository {
6+
@override
7+
Future<Result<String?>> getToken() async {
8+
return Result.ok('token');
9+
}
10+
11+
@override
12+
Future<Result<void>> saveToken(String? token) async {
13+
notifyListeners();
14+
return Result.ok(null);
15+
}
16+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import 'package:shared_preferences/shared_preferences.dart';
2+
3+
import '../../../utils/result.dart';
4+
import 'auth_token_repository.dart';
5+
6+
/// [AuthTokenRepository] that stores the token in Shared Preferences.
7+
/// Provided for demo purposes, consider using a secure store instead.
8+
class AuthTokenRepositorySharedPrefs extends AuthTokenRepository {
9+
static const _tokenKey = 'TOKEN';
10+
String? cachedToken;
11+
12+
@override
13+
Future<Result<String?>> getToken() async {
14+
if (cachedToken != null) return Result.ok(cachedToken);
15+
16+
try {
17+
final sharedPreferences = await SharedPreferences.getInstance();
18+
final token = sharedPreferences.getString(_tokenKey);
19+
return Result.ok(token);
20+
} on Exception catch (e) {
21+
return Result.error(e);
22+
}
23+
}
24+
25+
@override
26+
Future<Result<void>> saveToken(String? token) async {
27+
try {
28+
final sharedPreferences = await SharedPreferences.getInstance();
29+
if (token == null) {
30+
await sharedPreferences.remove(_tokenKey);
31+
} else {
32+
await sharedPreferences.setString(_tokenKey, token);
33+
}
34+
cachedToken = token;
35+
notifyListeners();
36+
return Result.ok(null);
37+
} on Exception catch (e) {
38+
return Result.error(e);
39+
}
40+
}
41+
}

compass_app/app/lib/data/services/api_client.dart

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,52 @@ import 'dart:io';
33
import 'package:compass_model/model.dart';
44

55
import '../../utils/result.dart';
6+
import '../repositories/auth/auth_token_repository.dart';
7+
8+
typedef AuthTokenProvider = Future<String?> Function();
69

7-
// TODO: Basic auth request
810
// TODO: Configurable baseurl/host/port
911
class ApiClient {
12+
ApiClient({
13+
required AuthTokenRepository authTokenRepository,
14+
}) : _authTokenRepository = authTokenRepository;
15+
16+
/// Provides the auth token to be used in the request
17+
final AuthTokenRepository _authTokenRepository;
18+
19+
Future<void> _authHeader(HttpHeaders headers) async {
20+
final result = await _authTokenRepository.getToken();
21+
if (result is Ok<String?>) {
22+
if (result.value != null) {
23+
headers.add(HttpHeaders.authorizationHeader, 'Bearer ${result.value}');
24+
}
25+
}
26+
}
27+
28+
Future<Result<LoginResponse>> login(LoginRequest loginRequest) async {
29+
final client = HttpClient();
30+
try {
31+
final request = await client.post('localhost', 8080, '/login');
32+
request.write(jsonEncode(loginRequest));
33+
final response = await request.close();
34+
if (response.statusCode == 200) {
35+
final stringData = await response.transform(utf8.decoder).join();
36+
return Result.ok(LoginResponse.fromJson(jsonDecode(stringData)));
37+
} else {
38+
return Result.error(const HttpException("Login error"));
39+
}
40+
} on Exception catch (error) {
41+
return Result.error(error);
42+
} finally {
43+
client.close();
44+
}
45+
}
46+
1047
Future<Result<List<Continent>>> getContinents() async {
1148
final client = HttpClient();
1249
try {
1350
final request = await client.get('localhost', 8080, '/continent');
51+
await _authHeader(request.headers);
1452
final response = await request.close();
1553
if (response.statusCode == 200) {
1654
final stringData = await response.transform(utf8.decoder).join();
@@ -31,6 +69,7 @@ class ApiClient {
3169
final client = HttpClient();
3270
try {
3371
final request = await client.get('localhost', 8080, '/destination');
72+
await _authHeader(request.headers);
3473
final response = await request.close();
3574
if (response.statusCode == 200) {
3675
final stringData = await response.transform(utf8.decoder).join();
@@ -52,6 +91,7 @@ class ApiClient {
5291
try {
5392
final request =
5493
await client.get('localhost', 8080, '/destination/$ref/activity');
94+
await _authHeader(request.headers);
5595
final response = await request.close();
5696
if (response.statusCode == 200) {
5797
final stringData = await response.transform(utf8.decoder).join();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import 'package:compass_model/model.dart';
2+
import 'package:logging/logging.dart';
3+
4+
import '../../../utils/result.dart';
5+
import '../../../data/repositories/auth/auth_token_repository.dart';
6+
import '../../../data/services/api_client.dart';
7+
8+
/// Performs user login.
9+
class AuthLoginComponent {
10+
AuthLoginComponent({
11+
required AuthTokenRepository authTokenRepository,
12+
required ApiClient apiClient,
13+
}) : _authTokenRepository = authTokenRepository,
14+
_apiClient = apiClient;
15+
16+
final AuthTokenRepository _authTokenRepository;
17+
final ApiClient _apiClient;
18+
final _log = Logger('AuthLoginComponent');
19+
20+
/// Login with username and password.
21+
/// Performs login with the server and stores the obtained auth token.
22+
Future<Result<void>> login({
23+
required String email,
24+
required String password,
25+
}) async {
26+
final result = await _apiClient.login(
27+
LoginRequest(
28+
email: email,
29+
password: password,
30+
),
31+
);
32+
switch (result) {
33+
case Ok<LoginResponse>():
34+
_log.info('User logged int');
35+
return await _authTokenRepository.saveToken(result.value.token);
36+
case Error<LoginResponse>():
37+
_log.warning('Error logging in: ${result.error}');
38+
return Result.error(result.error);
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)