Skip to content

OpenPecha/WeBuddhist-app

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

674 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

WeBuddhist App

A modern Flutter app for learning, sharing, and live collaboration.

πŸš€ Project Goal

WeBuddhist App is designed to help users learn, live, and share knowledge interactively. It features a beautiful UI, supports both light and dark themes, and is built with best Flutter practices.

πŸ›  Features

  • Light and Dark theme support with toggle
  • Modern Flutter architecture
  • Easy to customize and extend

πŸ“¦ Getting Started

1. Clone the Repository

git clone https://github.com/your-username/flutter_pecha.git
cd flutter_pecha

2. Install Dependencies

flutter pub get

3. Environment Setup

Create environment files from the template:

cp .env.example .env.dev
cp .env.example .env.staging
cp .env.example .env.prod

Edit each file with the appropriate values for that environment.

4. Run the App

Android

flutter run --flavor dev -t lib/main_dev.dart
flutter run --flavor staging -t lib/main_staging.dart
flutter run --flavor prod -t lib/main_prod.dart

Build APK:

flutter build apk --flavor dev -t lib/main_dev.dart
flutter build apk --flavor staging -t lib/main_staging.dart
flutter build apk --flavor prod -t lib/main_prod.dart

Build App Bundle:

flutter build appbundle --flavor prod -t lib/main_prod.dart

iOS

flutter run --flavor dev -t lib/main_dev.dart
flutter run --flavor staging -t lib/main_staging.dart
flutter run --flavor prod -t lib/main_prod.dart

Build IPA:

flutter build ios --flavor prod -t lib/main_prod.dart

Note:

  • For iOS, ensure you have Xcode installed and have granted the necessary permissions (see Flutter macOS setup).
  • For Android, ensure Android Studio and an emulator/device are set up.

5. Adding Localizations

The app uses Flutter's built-in internationalization (l10n) with ARB files for translations.

Localization files location: lib/core/l10n/

To add a new translation string:

  1. Add the key to all ARB files (app_en.arb, app_bo.arb, app_zh.arb):
// app_en.arb
"my_new_key": "My English text",

// app_bo.arb
"my_new_key": "My Tibetan text",

// app_zh.arb
"my_new_key": "My Chinese text",
  1. Generate localization files:
flutter gen-l10n
  1. Use in your widget:
import 'package:flutter_pecha/core/extensions/context_ext.dart';

// Access translation via context.l10n
Text(context.l10n.my_new_key)

For translations with parameters:

// app_en.arb
"greeting": "Hello {name}",
"@greeting": {
  "placeholders": {
    "name": {"type": "String"}
  }
}

Usage:

Text(context.l10n.greeting('John'))

βš™οΈ Project Structure

This project follows Clean Architecture principles with clear separation of concerns across three main layers.

Architecture Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    PRESENTATION LAYER                           β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  UI Components (Screens, Widgets)                       β”‚   β”‚
β”‚  β”‚  State Management (Riverpod Notifiers/Providers)        β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                              ↓                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓ depends on
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      DOMAIN LAYER                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Entities (Business Objects)                            β”‚   β”‚
β”‚  β”‚  Use Cases (Business Logic)                             β”‚   β”‚
β”‚  β”‚  Repository Interfaces (Contracts)                      β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                              ↓                                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓ depends on
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                       DATA LAYER                                β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚  Repository Implementations                             β”‚   β”‚
β”‚  β”‚  Data Sources (API, Local Storage)                      β”‚   β”‚
β”‚  β”‚  Models (DTOs for serialization)                        β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓ depends on
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    EXTERNAL SERVICES                            β”‚
β”‚         (Auth0, Firebase, HTTP, Storage, etc.)                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Folder Structure

lib/
β”œβ”€β”€ core/                    # Shared infrastructure & utilities
β”‚   β”œβ”€β”€ config/              # App configuration
β”‚   β”œβ”€β”€ di/                  # Dependency injection
β”‚   β”œβ”€β”€ error/               # Error handling (Failures)
β”‚   β”œβ”€β”€ network/             # Network utilities
β”‚   β”œβ”€β”€ services/            # External service integrations
β”‚   β”œβ”€β”€ storage/             # Local storage utilities
β”‚   └── theme/               # App theming
β”‚
β”œβ”€β”€ features/                # Feature-based modules
β”‚   └── auth/                # ← Authentication feature example
β”‚       β”œβ”€β”€ domain/          # Business logic (no dependencies)
β”‚       β”œβ”€β”€ data/            # Data implementation
β”‚       └── presentation/    # UI & state management
β”‚
└── shared/                  # Cross-cutting concerns
    β”œβ”€β”€ data/                # Shared data layer utilities
    β”œβ”€β”€ domain/              # Shared domain logic
    β”œβ”€β”€ presentation/        # Shared UI components
    └── widgets/             # Reusable widgets

Auth Flow Example

Here's how authentication follows clean architecture through all layers:

1. Domain Layer (features/auth/domain/)

Pure business logic with no framework dependencies

domain/
β”œβ”€β”€ entities/
β”‚   └── user.dart                    # User business entity
β”œβ”€β”€ repositories/
β”‚   └── auth_repository.dart         # Repository interface (contract)
└── usecases/
    β”œβ”€β”€ login_usecase.dart           # Login business logic
    β”œβ”€β”€ get_current_user_usecase.dart
    └── logout_usecase.dart

Key points:

  • User entity: Pure Dart class with business properties
  • AuthRepository: Abstract interface defining data operations
  • LoginUseCase: Orchestrates login logic, returns Either<Failure, User>

2. Data Layer (features/auth/data/)

Implements domain contracts, handles external dependencies

data/
β”œβ”€β”€ models/
β”‚   └── user_model.dart              # DTO for JSON serialization
β”œβ”€β”€ datasources/
β”‚   └── auth_remote_datasource.dart  # API/Service calls
└── repositories/
    └── auth_repository_impl.dart    # Implements AuthRepository

Key points:

  • UserModel: Handles JSON ↔ Dart conversion
  • AuthRemoteDataSource: Makes actual API calls
  • AuthRepositoryImpl: Implements domain interface, uses datasource

3. Presentation Layer (features/auth/presentation/)

UI and state management

presentation/
β”œβ”€β”€ providers/
β”‚   β”œβ”€β”€ auth_notifier.dart           # State management
β”‚   β”œβ”€β”€ auth_providers.dart          # DI setup
β”‚   └── use_case_providers.dart      # Use case providers
β”œβ”€β”€ screens/
β”‚   └── login_page.dart              # Login UI
└── widgets/
    └── login_form.dart              # Reusable form widget

Key points:

  • AuthNotifier: Manages auth state, calls use cases
  • authProviders: Wire up dependencies using Riverpod
  • LoginPage: UI that consumes state via providers

Key Principles

Layer Responsibility Dependencies
Domain Business rules & logic None (pure Dart)
Data Data sources & persistence Domain, external services
Presentation UI & state management Domain (via use cases)

Benefits

  • Testable: Each layer can be unit tested independently
  • Maintainable: Changes in one layer don't break others
  • Scalable: Easy to add new features following same pattern
  • Flexible: Swap implementations (e.g., change API) without affecting business logic

Dio Implementation

Dio is a powerful HTTP client for Dart. Think of it as a supercharged http package with built-in support for interceptors, retries, timeout handling, and request transformation.

Why Dio Over http Package?

Feature http package Dio
Interceptors No Yes (we use this heavily)
Global configuration Limited Full
Automatic retries Manual Built-in

Our DioClient Structure

Location: lib/core/network/dio_client.dart

class DioClient {
  DioClient({
    required AuthInterceptor authInterceptor,
    required RetryInterceptor retryInterceptor,
    // ... other interceptors
  }) : _dio = Dio(options) {
    // Interceptor order is critical
    _dio.interceptors.addAll([
      authInterceptor,      // 1. Add auth headers first
      cacheInterceptor,     // 2. Check cache
      retryInterceptor,     // 3. Handle 401 & network errors
      errorInterceptor,     // 4. Convert errors
      loggingInterceptor,   // 5. Log final result
    ]);
  }
}

Why this order? Each interceptor processes the request in sequence. Auth must run first to add tokens before the request goes out.

Interceptor Chain: How Requests Flow

Request:  Auth β†’ Cache β†’ Retry β†’ Error β†’ Log β†’ Server
Response: Log β†’ Error β†’ Retry β†’ Cache β†’ Auth β†’ UI

Interceptor 1: AuthInterceptor

What it does: Checks if the API endpoint requires authentication, adds the auth token.

// lib/core/network/interceptors/auth_interceptor.dart
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) async {
  // Only add auth for protected routes
  if (ProtectedRoutes.isProtected(options.path)) {
    final token = await _tokenProvider.getToken();
    if (token != null) {
      options.headers['Authorization'] = 'Bearer $token';
    }
  }
  handler.next(options);  // Pass to next interceptor
}

Key point: Uses TokenProvider abstraction so we can swap token sources without changing this code.

Interceptor 2: RetryInterceptor

What it does: Handles 401 (token expired) errors by refreshing the token and retrying the request.

// lib/core/network/interceptors/retry_interceptor.dart
@override
void onError(DioException err, ErrorInterceptorHandler handler) async {
  // Handle 401 - Token expired
  if (err.response?.statusCode == 401) {
    if (_isRefreshing) {
      // Already refreshing, queue this request
      _refreshQueue.add(_RetryRequest(err, handler));
      return;
    }

    _isRefreshing = true;
    final newToken = await _authService.refreshIdToken();

    // Retry all queued requests with new token
    for (final request in _refreshQueue) {
      request.error.requestOptions.headers['Authorization'] = 'Bearer $newToken';
      // Retry the request...
    }
  }

  // Retry network errors with exponential backoff
  if (_shouldRetry(err)) {
    await Future.delayed(Duration(milliseconds: 1000 * (1 << retryCount)));
    // Retry...
  }
}

OAuth Implementation

What is OAuth 2.0?

OAuth 2.0 is an authorization framework that lets users grant limited access to their accounts without sharing passwords.

Real-world analogy: Like giving a valet key to your car - it can only drive the car, not open the trunk.

Why OAuth 2.0?

  1. Security: User never shares password with your app
  2. Control: User can revoke access anytime
  3. Standardization: Industry-wide protocol
  4. Social Login: Leverage existing accounts

OAuth 2.0 Roles

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   User      β”‚                 β”‚ Auth0 Server β”‚
β”‚  (Resource  β”‚                 β”‚              β”‚
β”‚   Owner)    β”‚                 β”‚              β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                 β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
       β”‚                                β”‚
       β”‚ 1. Tap "Login with Google"     β”‚
       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚
       β”‚                                β”‚
       β”‚ 2. Show Google login page      β”‚
       │◄────────────────────────────────
       β”‚                                β”‚
       β”‚ 3. User authenticates           β”‚
       β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚
       β”‚                                β”‚
       β”‚ 4. Return tokens                β”‚
       │◄────────────────────────────────
       β”‚                                β”‚
       β”‚    (Access Token, ID Token,     β”‚
       β”‚     Refresh Token)              β”‚

Our Auth0 Implementation

Location: lib/features/auth/auth_service.dart

class AuthService {
  final Auth0 _auth0 = Auth0('YOUR_DOMAIN', 'YOUR_CLIENT_ID');

  Future<Credentials?> loginWithGoogle() async {
    final credentials = await _auth0.webAuthentication(scheme: 'org.pecha.app')
        .login(
          useHTTPS: true,
          parameters: {"connection": "google-oauth2"},
          scopes: {"openid", "profile", "email", "offline_access"},
        );

    // Auth0 SDK handles PKCE automatically
    // Credentials contain: accessToken, idToken, refreshToken, expiresIn

    await _auth0.credentialsManager.storeCredentials(credentials);
    return credentials;
  }
}

What scopes mean:

  • openid: Enables OIDC protocol
  • profile: Access to user profile data
  • email: Access to user email
  • offline_access: Enables refresh tokens

Login Flow: Step by Step

Step 1: UI - Login Button

// lib/features/auth/presentation/widgets/auth_buttons.dart
ElevatedButton(
  onPressed: () {
    ref.read(authProvider.notifier).login(connection: 'google-oauth2');
  },
  child: Text('Login with Google'),
)

Step 2: AuthNotifier - State Management

// lib/features/auth/presentation/providers/auth_notifier.dart
Future<void> login({String? connection}) async {
  state = state.copyWith(isLoading: true);

  final result = await _loginUseCase(LoginParams(connection: connection));

  result.fold(
    (failure) => state = state.copyWith(errorMessage: failure.message),
    (credentials) => _handleSuccessfulLogin(credentials),
  );
}

Step 3: UseCase - Orchestration

// lib/features/auth/domain/usecases/login_usecase.dart
Future<Either<Failure, AuthCredentials>> call(LoginParams params) async {
  switch (params.connection) {
    case 'google-oauth2':
      return await _repository.loginWithGoogle();
    case 'apple':
      return await _repository.loginWithApple();
  }
}

Step 4: Repository - Data Layer

// lib/features/auth/data/repositories/auth_repository_impl.dart
Future<Either<Failure, AuthCredentials>> loginWithGoogle() async {
  try {
    final credentials = await _authService.loginWithGoogle();
    return Right(_toAuthCredentials(credentials));
  } catch (e) {
    return Left(AuthenticationFailure('Login failed'));
  }
}

Step 5: AuthService - Auth0 Integration

// lib/features/auth/auth_service.dart
Future<Credentials?> loginWithGoogle() async {
  return _loginWithConnection('google-oauth2');
}

Future<Credentials?> _loginWithConnection(String connection) async {
  final credentials = await _auth0.webAuthentication(scheme: 'org.pecha.app')
      .login(
        useHTTPS: true,
        parameters: {"connection": connection},
        scopes: {"openid", "profile", "email", "offline_access"},
      );

  await _auth0.credentialsManager.storeCredentials(credentials);
  return credentials;
}

Auth State & Navigation

Location: lib/core/config/router/go_router.dart

final goRouterProvider = Provider<GoRouter>((ref) {
  return GoRouter(
    refreshListenable: GoRouterRefreshStream(ref.watch(authProvider.notifier).stream),
    redirect: (context, state) async {
      final authState = ref.watch(authProvider);

      // Unauthenticated trying to access protected route
      if (!authState.isLoggedIn && RouteConfig.isProtectedRoute(currentPath)) {
        return RouteConfig.login;  // Redirect to login
      }

      // Authenticated user on login page
      if (authState.isLoggedIn && currentPath == RouteConfig.login) {
        return RouteConfig.home;  // Redirect to home
      }

      return null;  // No redirect
    },
  );
});

How it works:

  • Router watches authProvider for state changes
  • When auth state changes, router re-evaluates redirect logic
  • Automatically redirects based on auth status

Logout Flow

// lib/features/auth/presentation/providers/auth_notifier.dart
Future<void> logout() async {
  // 1. Clear credentials from storage
  await _localLogoutUseCase(const NoParams());

  // 2. Clear user data
  await ref.read(userProvider.notifier).clearUser();

  // 3. Update state
  state = state.copyWith(isLoggedIn: false);

  // 4. Router automatically redirects to login
}

App Launch: Auth State Restoration

// lib/features/auth/presentation/providers/auth_notifier.dart
AuthNotifier(...) : super(const AuthState(isLoading: true)) {
  _restoreLoginState();  // Runs immediately on creation
}

Future<void> _restoreLoginState() async {
  // 1. Check for valid credentials
  final hasCredentials = await _hasValidCredentialsUseCase();

  if (hasCredentials) {
    // 2. Restore user data
    state = state.copyWith(isLoggedIn: true, isLoading: false);
    ref.read(userProvider.notifier).initializeUser();
  } else {
    // 3. Check for guest mode
    final isGuest = await _isGuestModeUseCase();
    state = state.copyWith(isLoggedIn: isGuest, isGuest: isGuest, isLoading: false);
  }
}

Complete Flow Diagram

End-to-End: From Login to API Call

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     AUTHENTICATION & API CALL FLOW                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

                        APP LAUNCH
                            β”‚
                            β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚   AuthNotifier created  β”‚
              β”‚   (isLoading: true)     β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
                          β–Ό
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚  Check stored creds     β”‚
              β”‚  (CredentialsManager)   β”‚
              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚                       β”‚
        Has Creds              No Creds
              β”‚                       β”‚
              β–Ό                       β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ isLoggedIn=true β”‚    β”‚ Check guest mode β”‚
    β”‚ isLoading=false β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β”‚
             β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”
             β”‚         Was Guest?  Not Guest
             β”‚              β”‚             β”‚
             β–Ό              β–Ό             β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Show Home   β”‚  β”‚Show Home β”‚  β”‚Show Loginβ”‚
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚                β”‚
           β”‚                β”‚
           β–Ό                β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚     USER TAPS LOGIN BUTTON       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ AuthNotifier.login() called      β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ LoginUseCase called              β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ AuthRepository.loginWithGoogle() β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ AuthService.loginWithGoogle()    β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Auth0 Web Auth opens             β”‚
    β”‚ (PKCE flow handled by SDK)       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ User authenticates with Google   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Credentials returned             β”‚
    β”‚ (access, id, refresh tokens)     β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Stored in CredentialsManager     β”‚
    β”‚ (Keychain/Keystore)              β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ AuthNotifier state updated       β”‚
    β”‚ (isLoggedIn: true)               β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ GoRouter redirects to Home       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ User fetches their plans         β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ UserPlansNotifier.fetchPlans()   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ GetPlansUseCase called           β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Repository.getUserPlans()        β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ DataSource calls Dio.get()       β”‚
    β”‚ URL: /users/me/plans             β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ REQUEST INTERCEPTOR CHAIN        β”‚
    β”‚                                  β”‚
    β”‚ 1. AuthInterceptor               β”‚
    β”‚    - Path is protected? YES      β”‚
    β”‚    - Get token from Provider     β”‚
    β”‚    - Provider calls AuthService  β”‚
    β”‚    - AuthService checks expiry   β”‚
    β”‚    - If expired, refreshes       β”‚
    β”‚    - Returns valid token         β”‚
    β”‚    - Adds Authorization header   β”‚
    β”‚                                  β”‚
    β”‚ 2. CacheInterceptor              β”‚
    β”‚    - Not in cache, proceed       β”‚
    β”‚                                  β”‚
    β”‚ 3. RetryInterceptor              β”‚
    β”‚    - No error, proceed           β”‚
    β”‚                                  β”‚
    β”‚ 4. Send to server                β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ SERVER RESPONSE: 200 OK          β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ RESPONSE INTERCEPTOR CHAIN       β”‚
    β”‚                                  β”‚
    β”‚ 1. RetryInterceptor              β”‚
    β”‚    - No 401, proceed             β”‚
    β”‚                                  β”‚
    β”‚ 2. CacheInterceptor              β”‚
    β”‚    - Store in cache              β”‚
    β”‚                                  β”‚
    β”‚ 3. ErrorInterceptor              β”‚
    β”‚    - No error, proceed           β”‚
    β”‚                                  β”‚
    β”‚ 4. LoggingInterceptor            β”‚
    β”‚    - Log success                 β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ DataSource parses JSON           β”‚
    β”‚ Returns List<UserPlanModel>      β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Repository maps to entities      β”‚
    β”‚ Returns Either<Failure, Plans>   β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ UseCase returns Either            β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Notifier folds Either            β”‚
    β”‚ Updates state with data          β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ UI rebuilds with plans data      β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

401 Error Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      401 TOKEN EXPIRED FLOW                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

    API Request with expired token
                  β”‚
                  β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Server returns 401 Unauthorized  β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ RetryInterceptor.onError()       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
                   β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Check: Has valid credentials?    β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                   β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚                 β”‚
         YES               NO
          β”‚                 β”‚
          β–Ό                 β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Check if     β”‚  β”‚ Pass error   β”‚
    β”‚ already     β”‚  β”‚ through      β”‚
    β”‚ refreshing? β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
     β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”
     β”‚           β”‚
  Refreshing   Not refreshing
     β”‚           β”‚
     β–Ό           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Queue   β”‚  β”‚ Set refreshing = β”‚
β”‚ request β”‚  β”‚ true             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                      β”‚
                      β–Ό
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚ Call AuthService     β”‚
            β”‚ .refreshIdToken()    β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚
                       β–Ό
            β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
            β”‚ Auth0 API renews     β”‚
            β”‚ using refresh token  β”‚
            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                       β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚                 β”‚
         Success           Failure
              β”‚                 β”‚
              β–Ό                 β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Store new     β”‚  β”‚ onAuthExpired  β”‚
    β”‚ credentials   β”‚  β”‚ callback       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β†’ Logout      β”‚
            β”‚          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
            β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Retry queued  β”‚
    β”‚ requests      β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
            β”‚
            β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Retry originalβ”‚
    β”‚ request       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
            β”‚
            β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ Set refreshingβ”‚
    β”‚ = false       β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

🀝 Contributing

Pull requests are welcome! For major changes, please open an issue first to discuss what you would like to change.

πŸ“„ License

MIT

Table of Contents Screen

  • Browse through organized text content lists with ease
  • View all available versions of each text in one convenient location

Reader Screen

  • Immersive reading experience with optimized formatting

About

The WeBuddhist app connects users to Buddhist scriptures in various languages app built with flutter. The app is available on Android and iOS.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors