File size: 2,819 Bytes
b24b684
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../../core/providers.dart';
import '../data/auth_repository.dart';
import '../domain/user_model.dart';

enum AuthStatus { unknown, unauthenticated, authenticated }

class AuthState {
  final AuthStatus status;
  final AppUser? user;

  const AuthState({this.status = AuthStatus.unknown, this.user});

  AuthState copyWith({AuthStatus? status, AppUser? user, bool clearUser = false}) {
    return AuthState(
      status: status ?? this.status,
      user: clearUser ? null : (user ?? this.user),
    );
  }

  String? get role => user?.role;
}

final authControllerProvider =
    NotifierProvider<AuthController, AuthState>(AuthController.new);

class AuthController extends Notifier<AuthState> {
  AuthRepository get _repo => ref.read(authRepositoryProvider);

  @override
  AuthState build() {
    // Wire global 401 handling to force logout.
    ref.read(dioClientProvider).onUnauthorized = () {
      // ignore: discarded_futures
      logout();
    };
    return const AuthState();
  }

  /// Restore session on app start.
  Future<void> bootstrap() async {
    final token = await ref.read(tokenStorageProvider).readToken();
    if (token == null || token.isEmpty) {
      state = state.copyWith(status: AuthStatus.unauthenticated);
      return;
    }
    try {
      final user = await _repo.me();
      state = AuthState(status: AuthStatus.authenticated, user: user);
    } catch (_) {
      await ref.read(tokenStorageProvider).clear();
      state = state.copyWith(status: AuthStatus.unauthenticated, clearUser: true);
    }
  }

  Future<String?> sendOtp(String phone) => _repo.sendOtp(phone);

  /// Verifies OTP, persists the session and loads the user.
  Future<void> verifyOtp({
    required String phone,
    required String otp,
    required String role,
    String? name,
  }) async {
    final result = await _repo.verifyOtp(
      phone: phone,
      otp: otp,
      role: role,
      name: name,
    );
    await ref.read(tokenStorageProvider).save(
          token: result.accessToken,
          refreshToken: result.refreshToken,
          role: result.role,
          userId: result.userId,
        );
    final user = await _repo.me();
    state = AuthState(status: AuthStatus.authenticated, user: user);
  }

  void setUser(AppUser user) {
    state = state.copyWith(user: user);
  }

  Future<void> logout() async {
    final refresh = await ref.read(tokenStorageProvider).readRefresh();
    if (refresh != null && refresh.isNotEmpty) {
      await _repo.logout(refresh); // best-effort server revoke
    }
    await ref.read(tokenStorageProvider).clear();
    state = const AuthState(status: AuthStatus.unauthenticated);
  }
}