Flutter如何使用BLoC更改主题并设置系统主题

st4*_*tic 0 dart flutter bloc flutter-bloc

我在 main.dart 中使用 Flutter bloc 来检查用户身份验证。现在我的应用程序中没有主题选择,它是根据设备上的主题以编程方式选择的。我希望我的 AuthBloc 检查并设置用户在应用程序启动时设置的主题。我有一个主题选择页面,用户可以在其中选择一个主题,默认情况下应该是系统主题,您也可以选择浅色和深色。\n我如何在我的 AuthBloc 中实现此功能?\n我的 main.dart

\n
 return BlocProvider(\n          create: (context) => AuthBloc(authRepo: AuthRepo()),\n          child: BlocBuilder<AuthBloc, AuthState>(\n              builder: (context, state) {\n                print(state);\n                return MaterialApp(\n                navigatorKey: navKey,\n                title: 'YiwuMart',\n                debugShowCheckedModeBanner: false,\n                theme: lightTheme,\n                darkTheme: darkTheme,\n                home: MainScreen(\n                  key: scakey,\n                ),\n              );\n            },\n          ),\n        );\n
Run Code Online (Sandbox Code Playgroud)\n

我的主题更改屏幕

\n
bool isSystemTheme = true;\n  bool isLightTheme = false;\n  bool isDarkTheme = false;\n\n  Row _buildThemeRow(String themeName, bool isSelected) {\n    return Row(\n      mainAxisAlignment: MainAxisAlignment.start,\n      children: [\n        SizedBox(width: 5.h),\n        Text(themeName, style: TextStyles.bodyStyle),\n        const Spacer(),\n        if (isSelected)\n          const Icon(\n            Icons.check,\n            color: Colors.grey,\n            size: 15,\n          ),\n      ],\n    );\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n        appBar: AppBar(\n          title: const Text('\xd0\x92\xd1\x8b\xd0\xb1\xd0\xbe\xd1\x80 \xd1\x82\xd0\xb5\xd0\xbc\xd1\x8b'),\n        ),\n        body: Container(\n          width: double.infinity,\n          margin: REdgeInsets.all(8.0),\n          decoration: BoxDecoration(\n            color: Theme.of(context).colorScheme.secondary,\n            borderRadius: BorderRadius.circular(8.0),\n          ),\n          child: Column(\n            crossAxisAlignment: CrossAxisAlignment.start,\n            mainAxisSize: MainAxisSize.min,\n            children: [\n              Container(\n                width: double.infinity,\n                padding: REdgeInsets.all(8.0),\n                decoration: BoxDecoration(\n                  color: Theme.of(context).colorScheme.secondary,\n                  borderRadius: BorderRadius.circular(8.0),\n                ),\n                child: Column(\n                  children: [\n                    GestureDetector(\n                      onTap: () {\n                        setState(() {\n                          isSystemTheme = true;\n                          isLightTheme = false;\n                          isDarkTheme = false;\n                        });\n                      },\n                      child: _buildThemeRow('\xd0\xa1\xd0\xb8\xd1\x81\xd1\x82\xd0\xb5\xd0\xbc\xd0\xbd\xd0\xb0\xd1\x8f', isSystemTheme),\n                    ),\n                    const Divider(),\n                    GestureDetector(\n                      onTap: () {\n                        setState(() {\n                          isSystemTheme = false;\n                          isLightTheme = true;\n                          isDarkTheme = false;\n                        });\n                      },\n                      child: _buildThemeRow('\xd0\xa1\xd0\xb2\xd0\xb5\xd1\x82\xd0\xbb\xd0\xb0\xd1\x8f', isLightTheme),\n                    ),\n                    const Divider(),\n                    GestureDetector(\n                      onTap: () {\n                        setState(() {\n                          isSystemTheme = false;\n                          isLightTheme = false;\n                          isDarkTheme = true;\n                        });\n                      },\n                      child: _buildThemeRow('\xd0\xa2\xd0\xb5\xd0\xbc\xd0\xbd\xd0\xb0\xd1\x8f', isDarkTheme),\n                    ),\n                  ],\n                ),\n              ),\n            ],\n          ),\n        ));\n  }\n
Run Code Online (Sandbox Code Playgroud)\n

我的 AuthBloc:

\n
class AuthBloc extends Bloc<AuthEvent, AuthState> {\n  final AbstractAuth authRepo;\n  late StreamSubscription _authenticationStatusSubscription;\n\n  AuthBloc({required this.authRepo}) : super(AuthInitial()) {\n    authRepo.getCookie();\n    authRepo.getFirebaseToken();\n    _checkAuthenticationStatus();\n    on<LoggedIn>((event, emit) {\n      emit(Authenticated(token: event.token));\n    });\n    on<LogoutEvent>((event, emit) async {});\n    on<LoginEvent>((event, emit) async {});\n    on<RegistrationEvent>((event, emit) async {});\n  }\n\n  void _checkAuthenticationStatus() async {\n    final isAuthenticated = await authRepo.getToken();\n    final isAuth = await Func().getInitParams();\n    if (isAuth.statusCode == 200) {\n      add(LoggedIn(token: isAuthenticated));\n    } else {\n      add(LogoutEvent());\n    }\n    _authenticationStatusSubscription =\n        Stream.periodic(const Duration(seconds: 30)).listen((_) async {\n      final isAuthenticated = await authRepo.getToken();\n      final isAuth = await Func().getInitParams();\n      Func().getUnreadCount();\n      if (isAuth.statusCode == 200) {\n        add(LoggedIn(token: isAuthenticated));\n      } else {\n        add(LogoutEvent());\n      }\n    });\n  }\n\n  @override\n  Future<void> close() {\n    _authenticationStatusSubscription.cancel();\n    return super.close();\n  }\n\n  Stream<AuthState> mapEventToState(AuthEvent event) async* {\n    if (event is AppStarted) {\n      final isAuthenticated = await authRepo.getToken();\n      if (isAuthenticated != '') {\n        yield Authenticated(token: isAuthenticated.toString());\n      } else {\n        yield Unauthenticated(token: isAuthenticated.toString());\n      }\n    } else if (event is LoggedIn) {\n      yield Authenticated(token: event.token);\n    } else if (event is LogoutEvent) {\n      yield Unauthenticated(token: '');\n    } else if (event is LoginEvent) {\n      final data =\n          await authRepo.login(event.email, event.password, event.context);\n      if (data['api_token'] != '') {\n        yield Authenticated(token: data['api_token']);\n      } else {\n        yield Unauthenticated(token: 'token');\n      }\n    }\n  }\n}\n// event \nabstract class AuthEvent {}\n\nclass AppStarted extends AuthEvent {}\n\nclass LoggedIn extends AuthEvent {\n  final String token;\n\n  LoggedIn({required this.token});\n}\n\nclass LogoutEvent extends AuthEvent {}\n\nclass LoginEvent extends AuthEvent {\n  final BuildContext context;\n  final String email;\n  final String password;\n\n  LoginEvent({required this.email, required this.password, required this.context});\n\n  List<Object> get props => [email, password, context];\n}\n\nclass RegistrationEvent extends AuthEvent {\n  final BuildContext context;\n  final String email;\n  final String password;\n  final String name;\n  final String surname;\n\n  RegistrationEvent(\n      {required this.email,\n      required this.password,\n      required this.name,\n      required this.surname,\n      required this.context});\n\n  List<Object> get props => [email, password, name, surname, context];\n}\n\n
Run Code Online (Sandbox Code Playgroud)\n

Lor*_*n.A 5

这里有几件事。

首先,我将创建一个单独的 Bloc/Cubit 来处理系统主题,因为这与身份验证无关。

mapEventToState其次,当前的 Bloc API 和已经弃用一年多的 API的奇怪组合。

因此,尽管它与您的问题无关,但我建议按照其预期的方式使用 Bloc,以充分利用它。这将需要实际使用您拥有的所有on<Event>((event, emit) async {})空事件处理程序并摆脱mapEventToState.

该转换的快速示例。

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  AuthBloc({required this.authRepo}) : super(AuthInitial()) {
    authRepo.getCookie();
    authRepo.getFirebaseToken();
    _checkAuthenticationStatus();
  
    on<LoggedIn>((event, emit) {
      emit(Authenticated(token: event.token));
    });
    
    on<AppStarted>(_onAppStarted); // added this event handler

    // you should use the rest of these event handlers and remove the mapEventToState method
    on<LogoutEvent>((event, emit) async {});
    on<LoginEvent>((event, emit) async {});
    on<RegistrationEvent>((event, emit) async {});
  }

  /// Example event handler method based on what you had in your mapEventToState
  Future<void> _onAppStarted(AppStarted event, Emitter<AuthState> emit) async {
    final isAuthenticated = await authRepo.getToken();
    if (isAuthenticated != '') {
      emit(Authenticated(token: isAuthenticated.toString()));
    } else {
      emit(Unauthenticated(token: isAuthenticated.toString()));
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

至于主题方面。我建议使用Hydrod_bloc,这样它就可以在重新启动后持续存在,而无需付出最小的努力。如果您已经有另一个存储解决方案,那么这也可以。

您可以创建一个非常简单的 ThemeCubit 类。它所做的只是更新并存储ThemeModeFlutter SDK 中的枚举。通过这种方式,您可以跟踪单个布尔值enum与 3 个单独的布尔值。

import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';

class ThemeCubit extends HydratedCubit<ThemeMode> {
  ThemeCubit() : super(ThemeMode.system);

  void updateTheme(ThemeMode themeMode) => emit(themeMode);

  // This handles the restoration of the theme mode when the app is restarted.
  @override
  ThemeMode? fromJson(Map<String, dynamic> json) {
    final theme = json['themeMode'];

    switch (theme) {
      case 'ThemeMode.system':
        return ThemeMode.system;
      case 'ThemeMode.light':
        return ThemeMode.light;
      case 'ThemeMode.dark':
        return ThemeMode.dark;
    }
    return ThemeMode.system;
  }

  // This stores the ThemeMode anytime its changed
  @override
  Map<String, dynamic>? toJson(ThemeMode state) {
    return {
      'themeMode': state.toString(),
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

那么你的材料应用程序可能看起来像这样

class BlocProviderWrapper extends StatelessWidget {
  const BlocProviderWrapper({super.key});

  @override
  Widget build(BuildContext context) {
    return MultiBlocProvider(
      providers: [
        BlocProvider(
          create: (context) => ThemeCubit(),
        ),
        BlocProvider<AuthBloc>(
          create: (context) => AuthBloc(),
        ),
      ],
      child: const App(),
    );
  }
}

class App extends StatelessWidget {
  const App({
    super.key,
  });

  ThemeData _getTheme(ThemeMode themeMode) {
    switch (themeMode) {
      case ThemeMode.system:

        /// Checks system brightness when user selects system theme mode
        final brightness =
            SchedulerBinding.instance.platformDispatcher.platformBrightness;
        return brightness == Brightness.light
            ? ThemeData.light()
            : ThemeData.dark();

      case ThemeMode.light:
        return ThemeData.light();

      case ThemeMode.dark:
        return ThemeData.dark();
    }
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ThemeCubit, ThemeMode>(
      builder: (context, state) {
        return MaterialApp(
          theme: _getTheme(state),
         // ... rest of your Material App
        );
      },
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

HydratedBloc 持久性的最后一件事是在 main 函数之前初始化存储runApp。这需要path_provider包来获取设备存储目录。

import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';


Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final directory = await getApplicationDocumentsDirectory(); // from path provider

  HydratedBloc.storage = await HydratedStorage.build(
    storageDirectory: directory,
  );

  
// ...the rest of your startup logic
}
Run Code Online (Sandbox Code Playgroud)