Template
chore: 初始化脚手架 — sunny_mochi 企业级 Flutter 模板
Core 基础设施:Flavor 三包体系、Drift+SQLCipher 加密数据库 7 拦截器 Dio 网络栈、CrashReporter 崩溃日志、Sealed Failure 错误体系 5 色板主题系统、Auth 认证骨架(Clean Architecture)、Dev Panel、Mock Adapter 通过验证:flutter analyze(0 errors)、flutter test(全绿)、staging APK 74.8MB
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:sunny_mochi/core/observability/talker_setup.dart';
|
||||
import 'package:sunny_mochi/features/auth/presentation/notifiers/auth_status_provider.dart';
|
||||
|
||||
import 'routes.dart';
|
||||
|
||||
part 'app_router.g.dart';
|
||||
|
||||
// 不需要登录即可访问的路径
|
||||
const _publicPaths = {'/login'};
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
GoRouter appRouter(Ref ref) {
|
||||
final auth = ref.read(authStatusProvider);
|
||||
|
||||
return GoRouter(
|
||||
debugLogDiagnostics: true,
|
||||
initialLocation: '/home',
|
||||
refreshListenable: auth.listenable,
|
||||
redirect: (BuildContext context, GoRouterState state) {
|
||||
final loggedIn = auth.value;
|
||||
final path = state.matchedLocation;
|
||||
appTalker.verbose('[Router] path=$path loggedIn=$loggedIn');
|
||||
|
||||
if (loggedIn == null) return null; // bootstrap 未完成,保持当前
|
||||
if (!loggedIn && !_publicPaths.contains(path)) return '/login';
|
||||
if (loggedIn && path == '/login') return '/home';
|
||||
return null;
|
||||
},
|
||||
routes: $appRoutes,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'app_router.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint, type=warning
|
||||
|
||||
@ProviderFor(appRouter)
|
||||
final appRouterProvider = AppRouterProvider._();
|
||||
|
||||
final class AppRouterProvider
|
||||
extends $FunctionalProvider<GoRouter, GoRouter, GoRouter>
|
||||
with $Provider<GoRouter> {
|
||||
AppRouterProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'appRouterProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$appRouterHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
$ProviderElement<GoRouter> $createElement($ProviderPointer pointer) =>
|
||||
$ProviderElement(pointer);
|
||||
|
||||
@override
|
||||
GoRouter create(Ref ref) {
|
||||
return appRouter(ref);
|
||||
}
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(GoRouter value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<GoRouter>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$appRouterHash() => r'6f5b7a9f1595fbf5343fdaf160df95a775d77b57';
|
||||
@@ -0,0 +1,89 @@
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:sunny_mochi/core/widgets/shell_scaffold.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:sunny_mochi/features/auth/presentation/pages/login_page.dart';
|
||||
import 'package:sunny_mochi/features/dev_panel/presentation/pages/dev_panel_page.dart';
|
||||
import 'package:sunny_mochi/features/error_report/presentation/error_report_page.dart';
|
||||
|
||||
part 'routes.g.dart';
|
||||
|
||||
// ── StatefulShellRoute(底部导航 2 Tab)─────────────────────────────────────
|
||||
|
||||
@TypedStatefulShellRoute<ShellRouteData>(
|
||||
branches: [
|
||||
TypedStatefulShellBranch<HomeBranch>(
|
||||
routes: [TypedGoRoute<HomeRoute>(path: '/home')],
|
||||
),
|
||||
TypedStatefulShellBranch<MineBranch>(
|
||||
routes: [TypedGoRoute<MineRoute>(path: '/mine')],
|
||||
),
|
||||
],
|
||||
)
|
||||
class ShellRouteData extends StatefulShellRouteData {
|
||||
const ShellRouteData();
|
||||
|
||||
@override
|
||||
Widget builder(
|
||||
BuildContext context,
|
||||
GoRouterState state,
|
||||
StatefulNavigationShell shell,
|
||||
) =>
|
||||
ShellScaffold(shell: shell);
|
||||
}
|
||||
|
||||
class HomeBranch extends StatefulShellBranchData {
|
||||
const HomeBranch();
|
||||
}
|
||||
|
||||
class MineBranch extends StatefulShellBranchData {
|
||||
const MineBranch();
|
||||
}
|
||||
|
||||
// ── Shell 叶子路由(TODO: 业务项目替换为真实页面)───────────────────────────
|
||||
|
||||
class HomeRoute extends GoRouteData with $HomeRoute {
|
||||
const HomeRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) => const Scaffold(
|
||||
body: Center(child: Text('Home — TODO: Replace with HomePage')),
|
||||
);
|
||||
}
|
||||
|
||||
class MineRoute extends GoRouteData with $MineRoute {
|
||||
const MineRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) => const Scaffold(
|
||||
body: Center(child: Text('Mine — TODO: Replace with MinePage')),
|
||||
);
|
||||
}
|
||||
|
||||
// ── 独立路由(Shell 之外)────────────────────────────────────────────────────
|
||||
|
||||
@TypedGoRoute<LoginRoute>(path: '/login')
|
||||
class LoginRoute extends GoRouteData with $LoginRoute {
|
||||
const LoginRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) =>
|
||||
const LoginPage();
|
||||
}
|
||||
|
||||
@TypedGoRoute<DevPanelRoute>(path: '/dev')
|
||||
class DevPanelRoute extends GoRouteData with $DevPanelRoute {
|
||||
const DevPanelRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) =>
|
||||
const DevPanelPage();
|
||||
}
|
||||
|
||||
@TypedGoRoute<ErrorReportRoute>(path: '/error-report')
|
||||
class ErrorReportRoute extends GoRouteData with $ErrorReportRoute {
|
||||
const ErrorReportRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) =>
|
||||
const ErrorReportPage();
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'routes.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// GoRouterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
List<RouteBase> get $appRoutes => [
|
||||
$shellRouteData,
|
||||
$loginRoute,
|
||||
$devPanelRoute,
|
||||
$errorReportRoute,
|
||||
];
|
||||
|
||||
RouteBase get $shellRouteData => StatefulShellRouteData.$route(
|
||||
factory: $ShellRouteDataExtension._fromState,
|
||||
branches: [
|
||||
StatefulShellBranchData.$branch(
|
||||
routes: [
|
||||
GoRouteData.$route(path: '/home', factory: $HomeRoute._fromState),
|
||||
],
|
||||
),
|
||||
StatefulShellBranchData.$branch(
|
||||
routes: [
|
||||
GoRouteData.$route(path: '/mine', factory: $MineRoute._fromState),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
extension $ShellRouteDataExtension on ShellRouteData {
|
||||
static ShellRouteData _fromState(GoRouterState state) =>
|
||||
const ShellRouteData();
|
||||
}
|
||||
|
||||
mixin $HomeRoute on GoRouteData {
|
||||
static HomeRoute _fromState(GoRouterState state) => const HomeRoute();
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/home');
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@override
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
@override
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
@override
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
mixin $MineRoute on GoRouteData {
|
||||
static MineRoute _fromState(GoRouterState state) => const MineRoute();
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/mine');
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@override
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
@override
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
@override
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
RouteBase get $loginRoute =>
|
||||
GoRouteData.$route(path: '/login', factory: $LoginRoute._fromState);
|
||||
|
||||
mixin $LoginRoute on GoRouteData {
|
||||
static LoginRoute _fromState(GoRouterState state) => const LoginRoute();
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/login');
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@override
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
@override
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
@override
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
RouteBase get $devPanelRoute =>
|
||||
GoRouteData.$route(path: '/dev', factory: $DevPanelRoute._fromState);
|
||||
|
||||
mixin $DevPanelRoute on GoRouteData {
|
||||
static DevPanelRoute _fromState(GoRouterState state) => const DevPanelRoute();
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/dev');
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@override
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
@override
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
@override
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
|
||||
RouteBase get $errorReportRoute => GoRouteData.$route(
|
||||
path: '/error-report',
|
||||
factory: $ErrorReportRoute._fromState,
|
||||
);
|
||||
|
||||
mixin $ErrorReportRoute on GoRouteData {
|
||||
static ErrorReportRoute _fromState(GoRouterState state) =>
|
||||
const ErrorReportRoute();
|
||||
|
||||
@override
|
||||
String get location => GoRouteData.$location('/error-report');
|
||||
|
||||
@override
|
||||
void go(BuildContext context) => context.go(location);
|
||||
|
||||
@override
|
||||
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
|
||||
|
||||
@override
|
||||
void pushReplacement(BuildContext context) =>
|
||||
context.pushReplacement(location);
|
||||
|
||||
@override
|
||||
void replace(BuildContext context) => context.replace(location);
|
||||
}
|
||||
Reference in New Issue
Block a user