Mztj67_#M}W?l>kYSliK<%xAp;0j{!}J0!o7b
zE>q9${Lb$D&h7k=+4=!ek^n+`0zq>LL1O?lVyea53S5x`Nqqo2YyeuIrQrJj9XjOp
z{;T5qbj3}&1vg1VK~#9!?b~^C5-}JC@Pyrv-6dSEqJqT}#j9#dJ@GzT@B8}x
zU&J@bBI>f6w6en+CeI)3^kC*U?}X%OD8$Fd$H&LV$H&LV$H&LV#|K5~mLYf|VqzOc
zkc7qL~0sOYuM{tG`rYEDV{DWY`Z8&)kW*hc2VkBuY+^Yx&92j&StN}Wp=LD
zxoGxXw6f&8sB^u})h@b@z0RBeD`K7RMR9deyL(ZJu#39Z>rT)^>v}Khq8U-IbIvT>
z?4pV9qGj=2)TNH3d)=De<+^w;>S7m_eFKTvzeaBeir45xY!^m!FmxnljbSS_3o=g(
z->^wC9%qkR{kbGnW8MfFew_o9h3(r55Is`L$8KI@d+*%{=Nx+FXJ98L0PjFIu;rGnnfY
zn1R5Qnp<{Jq0M1vX=X&F8gtLmcWv$1*M@4ZfF^9``()#hGTeKeP`1!iED
ztNE(TN}M5}3Bbc*d=FIv`DNv&@|C6yYj{sSqUj5oo$#*0$7pu|Dd2TLI>t5%I
zIa4Dvr(iayb+5x=j*Vum9&irk)xV1`t509lnPO0%skL8_1c#Xbamh(2@f?4yUI
zhhuT5<#8RJhGz4%b$`PJwKPAudsm|at?u;*hGgnA
zU1;9gnxVBC)wA(BsB`AW54N{|qmikJR*%x0c`{LGsSfa|NK61pYH(r-UQ4_JXd!Rsz)=k
zL{GMc5{h138)fF5CzHEDM>+FqY)$pdN3}Ml+riTgJOLN0F*Vh?{9ESR{SVVg>*>=#
zix;VJHPtvFFCRY$Ks*F;VX~%*r9F)W`PmPE9F!(&s#x07n2<}?S{(ygpXgX-&B&OM
zONY&BRQ(#%0%jeQs?oJ4P!p*R98>qCy5p8w>_gpuh39NcOlp)(wOoz0sY-Qz55eB~
z7OC-fKBaD1sE3$l-6QgBJO!n?QOTza`!S_YK
z_v-lm^7{VO^8Q@M_^8F)09Ki6%=s?2_5eupee(w1FB%aqSweusQ-T+CH0Xt{`
zFjMvW{@C&TB)k25()nh~_yJ9coBRL(0oO@HK~z}7?bm5j;y@69;bvlHb2tf!$ReA~x{22wTq550
z?f?Hnw(;m3ip30;QzdV~7pi!wyMYhDtXW#cO7T>|f=bdFhu+F!zMZ2UFj;GUKX7tI
z;hv3{q~!*pMj75WP_c}>6)IWvg5_yyg<9Op()eD1hWC19M@?_9_MHec{Z8n3FaF{8
z;u`Mw0ly(uE>*CgQYv{be6ab2LWhlaH1^iLIM{olnag$78^Fd}%dR7;JECQ+hmk|o
z!u2&!3MqPfP5ChDSkFSH8F2WVOEf0(E_M(JL17G}Y+fg0_IuW%WQ
zG(mG&u?|->YSdk0;8rc{yw2@2Z&GA}z{Wb91Ooz9VhA{b2DYE7RmG
zjL}?eq#iX%3#k;JWMx_{^2nNax`xPhByFiDX+a7uTGU|otOvIAUy|dEKkXOm-`aWS
z27pUzD{a)Ct<6p{{3)+lq@i`t@%>-wT4r?*S}k)58e09WZYP0{{R3FC5Sl00039P)t-s|Ns9~
z#rP?<_5oL$Q^olD{r_0T`27C={r>*`|Nj71npVa5OTzc(_WfbW_({R{p56NV{r*M2
z_xt?)2V0#0NsfV0u>{42ctGP(8vQj-Btk1n|O0ZD=YLwd&R{Ko41Gr9H=
zY@z@@bOAMB5Ltl$E>bJJ{>JP30ZxkmI%?eW{k`b?Wy<&gOo;dS`~CR$Vwb@XWtR|N
zi~t=w02?-0&j0TD{>bb6sNwsK*!p?V`RMQUl(*DVjk-9Cx+-z1KXab|Ka2oXhX5f%
z`$|e!000AhNklrxs)5QTeTVRiEmz~MKK1WAjCw(c-JK6eox;2O)?`?
zTG`AHia671e^vgmp!llKp|=5sVHk#C7=~epA~VAf-~%aPC=%Qw01h8mnSZ|p?hz91
z7p83F3%LVu9;S$tSI$C^%^yud1dfTM_6p2|+5Ejp$bd`GDvbR|xit>i!ZD&F>@CJrPmu*UjD&?DfZs=$@e3FQA(vNiU+$A*%a}
z?`XcG2jDxJ_ZQ#Md`H{4Lpf6QBDp81_KWZ6Tk#yCy1)32zO#3<7>b`eT7UyYH1eGz
z;O(rH$=QR*L%%ZcBpc=eGua?N55nD^K(8<#gl2+pN_j~b2MHs4#mcLmv%DkspS-3<
zpI1F=^9siI0s-;IN_IrA;5xm~3?3!StX}pUv0vkxMaqm+zxrg7X7(I&*N~&dEd0kD
z-FRV|g=|QuUsuh>-xCI}vD2imzYIOIdcCVV=$Bz@*u0+Bs<|L^)32nN*=wu3n%Ynw
z@1|eLG>!8ruU1pFXUfb`j>(=Gy~?Rn4QJ-c3%3T|(Frd!bI`9u&zAnyFYTqlG#&J7
zAkD(jpw|oZLNiA>;>hgp1KX7-wxC~31II47gc
zHcehD6Uxlf%+M^^uN5Wc*G%^;>D5qT{>=uxUhX%WJu^Z*(_Wq9y}npFO{Hhb>s6<9
zNi0pHXWFaVZnb)1+RS&F)xOv6&aeILcI)`k#0YE+?e)5r7J#c`3Z7x!LpTc01dx
zrdC3{Z;joZ^KN&))zB_i)I9fWedoN>Zl-6_Iz+^G&*ak2jpF07*qoM6N<$f;w%0(f|Me
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8
GIT binary patch
literal 1418
zcmV;51$Fv~P)q
zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+
zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq
z^={4hPQv)y=I|4n+?>7Fim=dxt1
z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT
zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf`
zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_>
z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3
zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF
z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a
z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE
z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62(
zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;?
zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-<
z{s<&cCV_1`^TD^ia9!*mQDq&
zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw
zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv
zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF
z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC
YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838
GIT binary patch
literal 68
zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J
Q1PU{Fy85}Sb4q9e0B4a5jsO4v
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838
GIT binary patch
literal 68
zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J
Q1PU{Fy85}Sb4q9e0B4a5jsO4v
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838
GIT binary patch
literal 68
zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J
Q1PU{Fy85}Sb4q9e0B4a5jsO4v
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
new file mode 100644
index 0000000..b988328
--- /dev/null
+++ b/ios/Runner/Info.plist
@@ -0,0 +1,49 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Xueguang Flutter App
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ xueguang_flutter_app
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/ios/RunnerTests/RunnerTests.swift b/ios/RunnerTests/RunnerTests.swift
new file mode 100644
index 0000000..86a7c3b
--- /dev/null
+++ b/ios/RunnerTests/RunnerTests.swift
@@ -0,0 +1,12 @@
+import Flutter
+import UIKit
+import XCTest
+
+class RunnerTests: XCTestCase {
+
+ func testExample() {
+ // If you add code to the Runner application, consider adding tests here.
+ // See https://developer.apple.com/documentation/xctest for more information about using XCTest.
+ }
+
+}
diff --git a/lib/config/config.dart b/lib/config/config.dart
new file mode 100644
index 0000000..9b5a7f5
--- /dev/null
+++ b/lib/config/config.dart
@@ -0,0 +1,16 @@
+class Config {
+ ///获取环境
+ static String getEnv() {
+ const env = String.fromEnvironment('ENV', defaultValue: 'dev');
+ return env;
+ }
+
+ ///获取接口地址
+ static String baseUrl() {
+ if (getEnv() == 'dev') {
+ return 'https://mindapp.test.tuzuu.com/api';
+ } else {
+ return 'https://mindapp.cells.org.cn/api';
+ }
+ }
+}
diff --git a/lib/config/theme/base/app_colors_base.dart b/lib/config/theme/base/app_colors_base.dart
new file mode 100644
index 0000000..a65e7d2
--- /dev/null
+++ b/lib/config/theme/base/app_colors_base.dart
@@ -0,0 +1,35 @@
+import 'dart:ui';
+
+abstract class AppColorsBase {
+ /// 品牌主色
+ Color get primary;
+
+ // 灰度
+ Color get textPrimary;
+
+ Color get textSecondary;
+
+ Color get textTertiary;
+
+ // 状态颜色
+ Color get success;
+
+ Color get warning;
+
+ Color get info;
+
+ Color get danger;
+
+
+ // 容器色
+ Color get surfaceContainerLowest;
+
+ Color get surfaceContainerLow;
+
+ Color get surfaceContainer;
+
+ Color get surfaceContainerHigh; //白色卡片 / item
+
+ //阴影颜色
+ Color get shadow;
+}
diff --git a/lib/config/theme/base/app_text_style.dart b/lib/config/theme/base/app_text_style.dart
new file mode 100644
index 0000000..6cec939
--- /dev/null
+++ b/lib/config/theme/base/app_text_style.dart
@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+import 'app_colors_base.dart'; // 假设你之前定义了 AppColorsBase
+
+TextTheme buildTextTheme(AppColorsBase colors) {
+ return TextTheme(
+ // 标题层级
+ titleLarge: TextStyle(
+ fontSize: 22, // 比正文大6
+ fontWeight: FontWeight.w700,
+ color: colors.textPrimary,
+ ),
+ titleMedium: TextStyle(
+ fontSize: 20, // 比正文大4
+ fontWeight: FontWeight.w700,
+ color: colors.textPrimary,
+ ),
+ titleSmall: TextStyle(
+ fontSize: 18, // 比正文大2
+ fontWeight: FontWeight.w700,
+ color: colors.textPrimary,
+ ),
+
+ // 正文字体
+ bodyLarge: TextStyle(
+ fontSize: 18, // 稍大正文
+ color: colors.textPrimary,
+ ),
+ bodyMedium: TextStyle(
+ fontSize: 16, // 正文标准
+ color: colors.textPrimary,
+ ),
+ bodySmall: TextStyle(
+ fontSize: 14, // 辅助正文
+ color: colors.textPrimary,
+ ),
+
+ // 标签/提示文字
+ labelLarge: TextStyle(
+ fontSize: 14, // 比正文小一点
+ fontWeight: FontWeight.w500,
+ color: colors.textSecondary,
+ ),
+ labelMedium: TextStyle(
+ fontSize: 12,
+ fontWeight: FontWeight.w500,
+ color: colors.textSecondary,
+ ),
+ labelSmall: TextStyle(
+ fontSize: 11,
+ fontWeight: FontWeight.w400,
+ color: colors.textSecondary,
+ ),
+ );
+}
diff --git a/lib/config/theme/base/app_theme_ext.dart b/lib/config/theme/base/app_theme_ext.dart
new file mode 100644
index 0000000..92b9b3d
--- /dev/null
+++ b/lib/config/theme/base/app_theme_ext.dart
@@ -0,0 +1,45 @@
+import 'package:flutter/material.dart';
+
+import 'app_colors_base.dart';
+
+@immutable
+class AppThemeExtension extends ThemeExtension {
+ final AppColorsBase baseTheme;
+
+ const AppThemeExtension({required this.baseTheme});
+
+ @override
+ ThemeExtension copyWith({
+ AppColorsBase? baseTheme,
+ }) {
+ return AppThemeExtension(
+ baseTheme: baseTheme ?? this.baseTheme,
+ );
+ }
+
+ @override
+ AppThemeExtension lerp(AppThemeExtension? other, double t) {
+ if (other is! AppThemeExtension) return this;
+ return t < 0.5 ? this : other; // 或者 this/base 混合逻辑
+ }
+}
+
+extension AppThemeExt on BuildContext {
+ AppThemeExtension get themeEx => Theme.of(this).extension()!;
+
+ ///主题
+ Color get success => themeEx.baseTheme.success;
+
+ Color get warning => themeEx.baseTheme.warning;
+
+ Color get danger => themeEx.baseTheme.danger;
+
+ Color get info => themeEx.baseTheme.info;
+
+ //字体灰度
+ Color get textSecondary => themeEx.baseTheme.textSecondary;
+
+ Color get textTertiary => themeEx.baseTheme.textTertiary;
+
+ double get pagePadding => 12;
+}
diff --git a/lib/config/theme/theme.dart b/lib/config/theme/theme.dart
new file mode 100644
index 0000000..9848f17
--- /dev/null
+++ b/lib/config/theme/theme.dart
@@ -0,0 +1,41 @@
+import 'package:flutter/material.dart';
+
+import 'base/app_colors_base.dart';
+import 'base/app_text_style.dart';
+import 'base/app_theme_ext.dart';
+
+export 'themes/light_theme.dart';
+export 'base/app_theme_ext.dart';
+
+class AppTheme {
+ static ThemeData createTheme(AppColorsBase themeBase) {
+ final textTheme = buildTextTheme(themeBase);
+ return ThemeData(
+ useMaterial3: true,
+ fontFamily: "资源圆体",
+ primaryColor: themeBase.primary,
+ scaffoldBackgroundColor: themeBase.surfaceContainerHigh,
+ colorScheme: ColorScheme.fromSeed(
+ seedColor: themeBase.primary,
+ brightness: Brightness.light,
+ onSurfaceVariant: themeBase.textSecondary,
+ //背景色
+ surfaceContainerHigh: themeBase.surfaceContainerHigh,
+ surfaceContainer: themeBase.surfaceContainer,
+ surfaceContainerLow: themeBase.surfaceContainerLow,
+ surfaceContainerLowest: themeBase.surfaceContainerLowest,
+ //阴影
+ shadow: themeBase.shadow,
+ ),
+ textTheme: textTheme,
+ extensions: [AppThemeExtension(baseTheme: themeBase)],
+ // pageTransitionsTheme: const PageTransitionsTheme(),
+ appBarTheme: AppBarTheme(
+ backgroundColor: Colors.white,
+ titleTextStyle: textTheme.titleMedium,
+ scrolledUnderElevation: 0,
+
+ ),
+ );
+ }
+}
diff --git a/lib/config/theme/themes/light_theme.dart b/lib/config/theme/themes/light_theme.dart
new file mode 100644
index 0000000..8d20068
--- /dev/null
+++ b/lib/config/theme/themes/light_theme.dart
@@ -0,0 +1,46 @@
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+
+import '../base/app_colors_base.dart';
+
+class LightTheme extends AppColorsBase {
+ @override
+ Color get primary => const Color(0xff615fff);
+
+ @override
+ Color get textPrimary => const Color(0xff222932);
+
+ @override
+ Color get textSecondary => const Color(0xffA6B0BE);
+
+ @override
+ Color get textTertiary => const Color(0xffC7CDD5);
+
+ @override
+ Color get success => const Color(0xff00D4B5);
+
+ @override
+ Color get warning => const Color(0xffFF9200);
+
+ @override
+ Color get info => const Color(0xFFEEEEEE);
+
+ @override
+ Color get danger => const Color(0xffFF4900);
+
+ @override
+ Color get surfaceContainerLowest => Color(0xffE0E0E0);
+
+ @override
+ Color get surfaceContainerLow => Color(0xffF0F0F0);
+
+ @override
+ Color get surfaceContainer => Color(0xfff7f9fa);
+
+ @override
+ Color get surfaceContainerHigh => Color(0xffFFFFFF);
+
+ @override
+ Color get shadow => const Color.fromRGBO(0, 0, 0, 0.1);
+}
diff --git a/lib/main.dart b/lib/main.dart
new file mode 100644
index 0000000..df250e7
--- /dev/null
+++ b/lib/main.dart
@@ -0,0 +1,35 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_easyloading/flutter_easyloading.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:app/router/routes.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+
+import 'config/theme/theme.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return ScreenUtilInit(
+ designSize: const Size(375, 694),
+ useInheritedMediaQuery: true,
+ child: MaterialApp.router(
+ locale: const Locale('zh'),
+ supportedLocales: const [
+ Locale('zh'),
+ Locale('en'),
+ ],
+ localizationsDelegates: GlobalMaterialLocalizations.delegates,
+ debugShowCheckedModeBanner: false,
+ routerConfig: goRouter,
+ theme: AppTheme.createTheme(LightTheme()),
+ builder: EasyLoading.init(),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/common/auth/login_page.dart b/lib/pages/common/auth/login_page.dart
new file mode 100644
index 0000000..a0c1f23
--- /dev/null
+++ b/lib/pages/common/auth/login_page.dart
@@ -0,0 +1,154 @@
+import 'dart:async';
+
+import 'package:app/router/route_paths.dart';
+import 'package:app/widgets/base/button/index.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_easyloading/flutter_easyloading.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:go_router/go_router.dart';
+import 'package:remixicon/remixicon.dart';
+
+import 'widgets/login_agree.dart';
+import 'widgets/login_input.dart';
+
+class LoginPage extends StatefulWidget {
+ const LoginPage({super.key});
+
+ @override
+ State createState() => _LoginPageState();
+}
+
+class _LoginPageState extends State {
+ ///协议
+ bool _agree = false;
+
+ ///输入框
+ final TextEditingController _telController = TextEditingController();
+ final TextEditingController _codeController = TextEditingController();
+
+ ///验证码倒计时
+ var _countDown = 0;
+ Timer? _timer;
+
+ @override
+ void initState() {
+ super.initState();
+ }
+
+ ///点击发送验证码
+ void _sendCode() async {
+ RegExp regExp = RegExp(r'^1\d{10}$');
+ if (!regExp.hasMatch(_telController.text)) {
+ EasyLoading.showToast("请填写正确的手机号");
+ return;
+ }
+ setState(() {
+ _countDown = 60;
+ });
+ _timer = Timer.periodic(Duration(seconds: 1), (timer) {
+ setState(() {
+ _countDown--;
+ });
+ if (_countDown <= 0) {
+ _timer?.cancel();
+ _timer = null;
+ }
+ });
+ }
+
+ ///提交登录
+ void _handSubmit() async {
+ RegExp regExp = RegExp(r'^1\d{10}$');
+ if (!regExp.hasMatch(_telController.text) || _codeController.text.isEmpty) {
+ EasyLoading.showToast("请填写完整手机号或验证码");
+ return;
+ }
+ context.go(RoutePaths.sHome);
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ _timer?.cancel();
+ _timer = null;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ //是否已发送验证码
+ bool codeOk = _countDown == 0;
+ return Scaffold(
+ resizeToAvoidBottomInset: false,
+ body: SafeArea(
+ child: Container(
+ padding: EdgeInsets.only(left: 20, right: 20, top: 0.08.sh, bottom: 40),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ margin: EdgeInsets.only(bottom: 40),
+ child: Text("登陆学光自习室", style: Theme.of(context).textTheme.titleLarge),
+ ),
+ LoginInput(
+ hintText: "请输入手机号",
+ controller: _telController,
+ suffix: Visibility(
+ visible: _telController.text.isNotEmpty,
+ child: InkWell(
+ onTap: () {
+ _telController.clear();
+ },
+ child: Icon(RemixIcons.close_circle_fill, size: 20),
+ ),
+ ),
+ ),
+ Container(
+ margin: EdgeInsets.only(top: 20),
+ child: Row(
+ spacing: 20,
+ children: [
+ Expanded(
+ child: LoginInput(
+ hintText: "输入验证码",
+ controller: _codeController,
+ maxLength: 4,
+ ),
+ ),
+ SizedBox(
+ height: 45,
+ child: Button(
+ text: codeOk ? "发送验证码" : "$_countDown 秒后发送",
+ radius: BorderRadius.circular(10),
+ disabled: !codeOk,
+ onPressed: _sendCode,
+ ),
+ ),
+ ],
+ ),
+ ),
+
+ Container(
+ margin: EdgeInsets.only(top: 40),
+ height: 50,
+ child: Button(text: "登 录", onPressed: _handSubmit),
+ ),
+ Container(
+ width: double.infinity,
+ margin: EdgeInsets.only(top: 20),
+ alignment: Alignment.center,
+ child: LoginAgree(
+ value: _agree,
+ onChanged: (value) {
+ setState(() {
+ _agree = value!;
+ });
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/common/auth/widgets/login_agree.dart b/lib/pages/common/auth/widgets/login_agree.dart
new file mode 100644
index 0000000..ae3ee22
--- /dev/null
+++ b/lib/pages/common/auth/widgets/login_agree.dart
@@ -0,0 +1,71 @@
+import 'package:app/router/route_paths.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+
+class LoginAgree extends StatelessWidget {
+ final ValueChanged? onChanged;
+ final bool value;
+
+ const LoginAgree({
+ super.key,
+ this.onChanged,
+ required this.value,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SizedBox(
+ width: 25,
+ child: Transform.scale(
+ scale: 0.8,
+ child: Checkbox(
+ value: value,
+ shape: CircleBorder(),
+ onChanged: onChanged,
+ ),
+ ),
+ ),
+ RichText(
+ text: TextSpan(
+ style: Theme.of(context).textTheme.labelMedium,
+ children: [
+ TextSpan(
+ text: "注册或登录即表示您了解并同意",
+ ),
+ TextSpan(
+ text: "服务条款",
+ style: TextStyle(color: Theme.of(context).primaryColor),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () => context.push(
+ RoutePaths.agreement,
+ extra: {
+ "title": "Terms of Service",
+ "url": "https://support.curain.ai/privacy/foodcura/terms_service.html",
+ },
+ ),
+ ),
+ TextSpan(text: " 和 "),
+ TextSpan(
+ text: "隐私协议",
+ style: TextStyle(color: Theme.of(context).primaryColor),
+ recognizer: TapGestureRecognizer()
+ ..onTap = () => context.push(
+ RoutePaths.agreement,
+ extra: {
+ "title": "Privacy",
+ "url": "https://support.curain.ai/privacy/foodcura/privacy_policy.html",
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages/common/auth/widgets/login_input.dart b/lib/pages/common/auth/widgets/login_input.dart
new file mode 100644
index 0000000..90b8740
--- /dev/null
+++ b/lib/pages/common/auth/widgets/login_input.dart
@@ -0,0 +1,49 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+class LoginInput extends StatelessWidget {
+ final String hintText;
+ final int maxLength;
+ final TextEditingController controller;
+ final Widget? suffix;
+
+ const LoginInput({
+ super.key,
+ required this.hintText,
+ this.maxLength = 11,
+ required this.controller,
+ this.suffix,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return TextField(
+ controller: controller,
+ maxLength: maxLength,
+ keyboardType: TextInputType.number,
+ inputFormatters: [
+ FilteringTextInputFormatter.digitsOnly, // 只允许 0-9
+ ],
+ decoration: InputDecoration(
+ hintText: hintText,
+ hintStyle: Theme.of(context).textTheme.labelLarge?.copyWith(fontSize: 16),
+ counterText: '',
+ filled: true,
+ fillColor: Theme.of(context).colorScheme.surfaceContainer,
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(60),
+ borderSide: BorderSide.none, // 去掉边框
+ ),
+ isCollapsed: true,
+ contentPadding: EdgeInsets.symmetric(vertical: 14, horizontal: 20),
+ suffixIconConstraints: BoxConstraints(minWidth: 0, minHeight: 0),
+ suffixIcon: suffix != null
+ ? Container(
+ padding: EdgeInsets.only(right: 20),
+ child: suffix,
+ )
+ : null,
+ ),
+ );
+ }
+}
diff --git a/lib/pages/common/splash/splash_page.dart b/lib/pages/common/splash/splash_page.dart
new file mode 100644
index 0000000..10fc100
--- /dev/null
+++ b/lib/pages/common/splash/splash_page.dart
@@ -0,0 +1,30 @@
+import 'package:app/router/route_paths.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+class SplashPage extends StatefulWidget {
+ const SplashPage({super.key});
+
+ @override
+ State createState() => _SplashPageState();
+}
+
+class _SplashPageState extends State {
+ @override
+ void initState() {
+ super.initState();
+ initPermission();
+ }
+
+ ///权限效验初始化
+ void initPermission() async {
+ WidgetsBinding.instance.addPostFrameCallback((_) async {
+ context.go(RoutePaths.login);
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold();
+ }
+}
diff --git a/lib/pages/student/home/s_home_page.dart b/lib/pages/student/home/s_home_page.dart
new file mode 100644
index 0000000..822bf4b
--- /dev/null
+++ b/lib/pages/student/home/s_home_page.dart
@@ -0,0 +1,36 @@
+import 'package:app/config/theme/base/app_theme_ext.dart';
+import 'package:flutter/material.dart';
+
+import 'today/s_today_card.dart';
+import 'widgets/user_header.dart';
+
+class SHomePage extends StatefulWidget {
+ const SHomePage({super.key});
+
+ @override
+ State createState() => _SHomePageState();
+}
+
+class _SHomePageState extends State {
+ ///刷新状态
+ Future _refresh() async {
+ await Future.delayed(Duration(seconds: 1));
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
+ appBar: UserHeader(),
+ body: RefreshIndicator(
+ onRefresh: _refresh,
+ child: ListView(
+ padding: EdgeInsets.all(context.pagePadding),
+ children: [
+ STodayCard(),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/student/home/today/banner_info.dart b/lib/pages/student/home/today/banner_info.dart
new file mode 100644
index 0000000..339b558
--- /dev/null
+++ b/lib/pages/student/home/today/banner_info.dart
@@ -0,0 +1,85 @@
+import 'package:app/config/theme/base/app_theme_ext.dart';
+import 'package:flutter/material.dart';
+
+///banner
+class BannerInfo extends StatelessWidget {
+ const BannerInfo({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ children: [
+ Positioned.fill(
+ child: Image.network(
+ "https://images.unsplash.com/photo-1505209487757-5114235191e5?w=800",
+ fit: BoxFit.cover,
+ ),
+ ),
+ Positioned.fill(
+ child: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ colors: [
+ Theme.of(context).primaryColor,
+ Theme.of(context).primaryColor.withValues(alpha: 0.5),
+ ],
+ begin: Alignment.centerLeft,
+ end: Alignment.centerRight,
+ ),
+ ),
+ ),
+ ),
+ Container(
+ width: double.infinity,
+ padding: EdgeInsets.all(context.pagePadding),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Container(
+ padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ margin: EdgeInsets.only(bottom: 30),
+ decoration: BoxDecoration(
+ color: Colors.black26,
+ borderRadius: BorderRadius.circular(30),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ spacing: 5,
+ children: [
+ Container(
+ width: 15,
+ height: 15,
+ decoration: BoxDecoration(
+ color: context.success,
+ shape: BoxShape.circle,
+ ),
+ ),
+ Text(
+ "进行中",
+ style: TextStyle(color: Colors.white, fontSize: 14),
+ ),
+ ],
+ ),
+ ),
+ Text(
+ "高中数学专场",
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ Container(
+ margin: EdgeInsets.only(top: 5),
+ child: Text(
+ "专业老师在线陪伴,专注高效学习",
+ style: TextStyle(color: Colors.white70, fontSize: 14),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/pages/student/home/today/s_today_card.dart b/lib/pages/student/home/today/s_today_card.dart
new file mode 100644
index 0000000..c8f8b94
--- /dev/null
+++ b/lib/pages/student/home/today/s_today_card.dart
@@ -0,0 +1,127 @@
+import 'package:app/config/theme/base/app_theme_ext.dart';
+import 'package:app/router/route_paths.dart';
+import 'package:app/widgets/base/button/index.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:remixicon/remixicon.dart';
+
+import 'banner_info.dart';
+import 'teacher_info.dart';
+
+class STodayCard extends StatefulWidget {
+ const STodayCard({super.key});
+
+ @override
+ State createState() => _STodayCardState();
+}
+
+class _STodayCardState extends State {
+
+ ///进入自习室
+ void _handleEnterRoom() {
+ context.push(RoutePaths.sRoom);
+ }
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ clipBehavior: Clip.hardEdge,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ BannerInfo(),
+ Container(
+ width: double.infinity,
+ padding: EdgeInsets.symmetric(horizontal: context.pagePadding, vertical: 20),
+ decoration: BoxDecoration(
+ color: Colors.white,
+ ),
+ child: Column(
+ spacing: 30,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ TeacherInfo(),
+ Row(
+ spacing: 20,
+ children: [
+ InfoItem(
+ label: "自习时间",
+ value: "19:00-21:00",
+ icon: RemixIcons.time_line,
+ color: Theme.of(context).primaryColor,
+ ),
+ InfoItem(
+ label: "在线人数",
+ value: "8/12 人",
+ icon: RemixIcons.time_line,
+ color: context.success,
+ ),
+ ],
+ ),
+ SizedBox(
+ height: 50,
+ child: Button(
+ text: "进入自习室",
+ onPressed: _handleEnterRoom,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+///信息item
+class InfoItem extends StatelessWidget {
+ final String label;
+ final String value;
+ final IconData icon;
+ final Color color;
+
+ const InfoItem({
+ super.key,
+ required this.label,
+ required this.value,
+ required this.icon,
+ required this.color,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Expanded(
+ child: Container(
+ padding: EdgeInsets.all(context.pagePadding),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Row(
+ spacing: 10,
+ children: [
+ Container(
+ width: 40,
+ height: 40,
+ decoration: BoxDecoration(
+ color: color.withValues(alpha: 0.1),
+ shape: BoxShape.circle,
+ ),
+ child: Icon(icon, color: color),
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(label, style: Theme.of(context).textTheme.labelMedium),
+ Text(value, style: TextStyle(fontSize: 16)),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/student/home/today/teacher_info.dart b/lib/pages/student/home/today/teacher_info.dart
new file mode 100644
index 0000000..fe53e86
--- /dev/null
+++ b/lib/pages/student/home/today/teacher_info.dart
@@ -0,0 +1,54 @@
+
+//老师信息
+import 'package:app/config/theme/base/app_theme_ext.dart';
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+
+class TeacherInfo extends StatelessWidget {
+ const TeacherInfo({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: EdgeInsets.all(context.pagePadding),
+ decoration: BoxDecoration(
+ color: Color(0xffeef2ff),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Row(
+ spacing: 15,
+ children: [
+ Container(
+ width: 60,
+ height: 60,
+ clipBehavior: Clip.hardEdge,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ border: Border.all(
+ color: Colors.white,
+ width: 3,
+ ),
+ ),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(50),
+ child: CachedNetworkImage(
+ imageUrl: 'https://doaf.asia/api/assets/1/图/62865798_p0.jpg',
+ fit: BoxFit.cover,
+ ),
+ ),
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text("张老师"),
+ Text(
+ "资深数学教师 · 10年教学经验",
+ style: Theme.of(context).textTheme.labelLarge,
+ ),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/pages/student/home/widgets/feature_static.dart b/lib/pages/student/home/widgets/feature_static.dart
new file mode 100644
index 0000000..6399039
--- /dev/null
+++ b/lib/pages/student/home/widgets/feature_static.dart
@@ -0,0 +1,52 @@
+import 'package:flutter/material.dart';
+import 'package:remixicon/remixicon.dart';
+
+class FeatureStatic extends StatelessWidget {
+ const FeatureStatic({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ final List items = [
+ FeatureItem("视频陪学", "老师全程在线监督", RemixIcons.video_on_ai_line),
+ FeatureItem("举手提问", "实时互动解答疑惑", RemixIcons.hand),
+ FeatureItem("拍照题目", "快速上传问题截图", RemixIcons.camera_2_line),
+ FeatureItem("文件共享", "支持PDF等多种格式", RemixIcons.upload_2_line),
+ ];
+ return Container(
+ margin: EdgeInsets.only(top: 15),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ spacing: 15,
+ children: [
+ Text("核心功能", style: TextStyle(fontSize: 18)),
+ GridView.builder(
+ shrinkWrap: true,
+ physics: NeverScrollableScrollPhysics(),
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 4,
+ mainAxisSpacing: 15,
+ crossAxisSpacing: 15,
+ mainAxisExtent: 120
+ ),
+ itemBuilder: (_, index) {
+ return Container(
+ decoration: BoxDecoration(
+ color: Colors.white
+ ),
+ );
+ },
+ itemCount: items.length,
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class FeatureItem {
+ final String title;
+ final String desc;
+ final IconData icon;
+
+ FeatureItem(this.title, this.desc, this.icon);
+}
diff --git a/lib/pages/student/home/widgets/user_header.dart b/lib/pages/student/home/widgets/user_header.dart
new file mode 100644
index 0000000..9ac7159
--- /dev/null
+++ b/lib/pages/student/home/widgets/user_header.dart
@@ -0,0 +1,44 @@
+import 'package:flutter/material.dart';
+import 'package:remixicon/remixicon.dart';
+
+class UserHeader extends StatelessWidget implements PreferredSizeWidget {
+ const UserHeader({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return AppBar(
+ title: const Text('学光自习室'),
+ actions: [
+ Container(
+ padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
+ decoration: BoxDecoration(
+ gradient: const LinearGradient(
+ colors: [Color(0xffffb900), Color(0xffff8904)],
+ begin: Alignment.centerLeft,
+ end: Alignment.centerRight,
+ ),
+ borderRadius: BorderRadius.circular(20),
+ ),
+ child: Row(
+ spacing: 5,
+ children: [
+ const Icon(
+ RemixIcons.vip_crown_line,
+ color: Colors.white,
+ size: 18,
+ ),
+ Text(
+ "会员至 2025-03-12",
+ style: TextStyle(color: Colors.white, fontSize: 14),
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 15),
+ ],
+ );
+ }
+
+ @override
+ Size get preferredSize => const Size.fromHeight(kToolbarHeight);
+}
diff --git a/lib/pages/student/room/controls/bottom_bar.dart b/lib/pages/student/room/controls/bottom_bar.dart
new file mode 100644
index 0000000..e763df2
--- /dev/null
+++ b/lib/pages/student/room/controls/bottom_bar.dart
@@ -0,0 +1,92 @@
+import 'package:app/widgets/room/file_drawer.dart';
+import 'package:flutter/material.dart';
+import 'package:remixicon/remixicon.dart';
+
+
+class BottomBar extends StatefulWidget {
+ const BottomBar({super.key});
+
+ @override
+ State createState() => _BottomBarState();
+}
+
+class _BottomBarState extends State {
+ ///显示文件
+ void _handShowFile() {
+ showFileDialog(context);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: BoxDecoration(
+ color: Color(0xff232426),
+ ),
+ height: 70,
+ child: Row(
+ children: [
+ BarItem(
+ title: "摄像头",
+ icon: RemixIcons.video_on_fill,
+ ),
+ BarItem(
+ title: "麦克风",
+ icon: RemixIcons.mic_off_fill,
+ ),
+ BarItem(
+ title: "已静音",
+ icon: RemixIcons.volume_mute_fill,
+ isOff: true,
+ ),
+ BarItem(
+ title: "举手",
+ icon: RemixIcons.hand,
+ ),
+ BarItem(
+ title: "拍照",
+ icon: RemixIcons.upload_2_fill,
+ onTap: _handShowFile,
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+class BarItem extends StatelessWidget {
+ final String title;
+ final IconData icon;
+ final bool isOff;
+ final void Function()? onTap;
+
+ const BarItem({
+ super.key,
+ required this.title,
+ required this.icon,
+ this.isOff = false,
+ this.onTap,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Expanded(
+ child: InkWell(
+ onTap: onTap,
+ child: Container(
+ color: isOff ? Colors.red : null,
+ child: Column(
+ spacing: 3,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(icon, color: Colors.white),
+ Text(
+ title,
+ style: TextStyle(color: Colors.white70, fontSize: 12),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/student/room/controls/top_bar.dart b/lib/pages/student/room/controls/top_bar.dart
new file mode 100644
index 0000000..76933b5
--- /dev/null
+++ b/lib/pages/student/room/controls/top_bar.dart
@@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+import 'package:remixicon/remixicon.dart';
+
+class TopBar extends StatelessWidget implements PreferredSizeWidget {
+ final bool showOther;
+ final void Function()? onOther;
+
+ const TopBar({super.key, this.showOther = false, this.onOther});
+
+ @override
+ Widget build(BuildContext context) {
+ return AppBar(
+ foregroundColor: Colors.white,
+ titleTextStyle: TextStyle(color: Colors.white, fontSize: 18),
+ backgroundColor: Color(0xff232426),
+ centerTitle: true,
+ title: Column(
+ children: [
+ Text("会议"),
+ Text(
+ "01:12",
+ style: TextStyle(fontSize: 12, color: Colors.white24),
+ ),
+ ],
+ ),
+ actions: [
+ IconButton(
+ onPressed: onOther,
+ icon: Icon(showOther ? RemixIcons.team_fill : RemixIcons.team_line),
+ ),
+ ],
+ );
+ }
+
+ @override
+ Size get preferredSize => const Size.fromHeight(kToolbarHeight);
+}
diff --git a/lib/pages/student/room/s_room_page.dart b/lib/pages/student/room/s_room_page.dart
new file mode 100644
index 0000000..b8bc4a5
--- /dev/null
+++ b/lib/pages/student/room/s_room_page.dart
@@ -0,0 +1,86 @@
+import 'package:app/widgets/base/transition/slide_hide.dart';
+import 'package:flutter/material.dart';
+
+import 'controls/bottom_bar.dart';
+import 'controls/top_bar.dart';
+import 'video/student_video_list.dart';
+import 'video/teacher_video.dart';
+
+class SRoomPage extends StatefulWidget {
+ const SRoomPage({super.key});
+
+ @override
+ State createState() => _SRoomPageState();
+}
+
+class _SRoomPageState extends State {
+ /// 显示控制栏
+ bool _controlsVisible = true;
+
+ ///显示其他学生画面
+ bool _showOtherStudent = true;
+
+ ///切换显示控制栏
+ void _toggleOverlay() {
+ setState(() {
+ _controlsVisible = !_controlsVisible;
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: Stack(
+ children: [
+ //底部控制显示
+ GestureDetector(
+ onTap: _toggleOverlay,
+ child: Container(color: Color(0xff2c3032)),
+ ),
+
+ //老师视频画面
+ TeacherVideo(),
+
+ Positioned(
+ right: 0,
+ top: 0,
+ bottom: 0,
+ child: Visibility(
+ visible: _showOtherStudent,
+ child: StudentVideoList(),
+ ),
+ ),
+
+ ///控制栏
+ Positioned(
+ top: 0,
+ left: 0,
+ right: 0,
+ child: SlideHide(
+ direction: SlideDirection.up,
+ hide: !_controlsVisible,
+ child: TopBar(
+ showOther: _showOtherStudent,
+ onOther: () {
+ setState(() {
+ _showOtherStudent = !_showOtherStudent;
+ });
+ },
+ ),
+ ),
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ child: SlideHide(
+ direction: SlideDirection.down,
+ hide: !_controlsVisible,
+ child: BottomBar(),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/student/room/video/student_video_list.dart b/lib/pages/student/room/video/student_video_list.dart
new file mode 100644
index 0000000..808cc6a
--- /dev/null
+++ b/lib/pages/student/room/video/student_video_list.dart
@@ -0,0 +1,64 @@
+import 'package:flutter/material.dart';
+
+class StudentVideoList extends StatefulWidget {
+ const StudentVideoList({super.key});
+
+ @override
+ State createState() => _StudentVideoListState();
+}
+
+class _StudentVideoListState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return SafeArea(
+ child: Container(
+ width: 250,
+ padding: EdgeInsets.only(bottom: 30),
+ child: ListView.separated(
+ padding: EdgeInsets.all(10),
+ itemCount: 8,
+ itemBuilder: (context, index) {
+ return VideoItem();
+ },
+ separatorBuilder: (context, index) => SizedBox(height: 15),
+ ),
+ ),
+ );
+ }
+}
+
+class VideoItem extends StatelessWidget {
+ const VideoItem({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ children: [
+ AspectRatio(
+ aspectRatio: 1.5 / 1,
+ child: Container(
+ decoration: BoxDecoration(
+ color: Color(0xff373c3e),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ ),
+ ),
+ Positioned(
+ bottom: 5,
+ left: 5,
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 7, vertical: 3),
+ decoration: BoxDecoration(
+ color: Colors.black26,
+ borderRadius: BorderRadius.circular(5),
+ ),
+ child: Text(
+ "小红",
+ style: TextStyle(fontSize: 12, color: Colors.white),
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/pages/student/room/video/teacher_video.dart b/lib/pages/student/room/video/teacher_video.dart
new file mode 100644
index 0000000..2880368
--- /dev/null
+++ b/lib/pages/student/room/video/teacher_video.dart
@@ -0,0 +1,22 @@
+import 'package:flutter/material.dart';
+
+class TeacherVideo extends StatefulWidget {
+ const TeacherVideo({super.key});
+
+ @override
+ State createState() => _TeacherVideoState();
+}
+
+class _TeacherVideoState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return SafeArea(
+ child: Align(
+ child: Text(
+ "画面准备中",
+ style: TextStyle(color: Colors.white),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/teacher/home/t_home_page.dart b/lib/pages/teacher/home/t_home_page.dart
new file mode 100644
index 0000000..0fef0cc
--- /dev/null
+++ b/lib/pages/teacher/home/t_home_page.dart
@@ -0,0 +1,28 @@
+import 'package:app/config/theme/base/app_theme_ext.dart';
+import 'package:flutter/material.dart';
+
+import 'widgets/header.dart';
+import 'widgets/today_card.dart';
+
+class THomePage extends StatefulWidget {
+ const THomePage({super.key});
+
+ @override
+ State createState() => _THomePageState();
+}
+
+class _THomePageState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
+ appBar: Header(),
+ body: ListView(
+ padding: EdgeInsets.symmetric(vertical: 20, horizontal: context.pagePadding),
+ children: [
+ TodayCard(),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/teacher/home/widgets/header.dart b/lib/pages/teacher/home/widgets/header.dart
new file mode 100644
index 0000000..1013b77
--- /dev/null
+++ b/lib/pages/teacher/home/widgets/header.dart
@@ -0,0 +1,49 @@
+import 'package:app/config/theme/base/app_theme_ext.dart';
+import 'package:flutter/material.dart';
+import 'package:remixicon/remixicon.dart';
+
+class Header extends StatelessWidget implements PreferredSizeWidget {
+ const Header({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ color: Colors.white,
+ child: SafeArea(
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: context.pagePadding),
+ height: preferredSize.height,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ "学光自习室",
+ style: Theme.of(context).textTheme.titleSmall,
+ ),
+ Text(
+ "在线陪伴、用心教学",
+ style: Theme.of(context).textTheme.labelMedium,
+ ),
+ ],
+ ),
+ Row(
+ children: [
+ IconButton(
+ onPressed: () {},
+ icon: Icon(RemixIcons.user_line),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ @override
+ Size get preferredSize => const Size.fromHeight(kToolbarHeight);
+}
diff --git a/lib/pages/teacher/home/widgets/today_card.dart b/lib/pages/teacher/home/widgets/today_card.dart
new file mode 100644
index 0000000..0edff38
--- /dev/null
+++ b/lib/pages/teacher/home/widgets/today_card.dart
@@ -0,0 +1,128 @@
+import 'package:app/router/route_paths.dart';
+import 'package:app/widgets/base/button/index.dart';
+import 'package:app/widgets/base/card/g_card.dart';
+import 'package:app/widgets/base/config/config.dart';
+import 'package:app/widgets/base/tag/index.dart';
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:remixicon/remixicon.dart';
+
+class TodayCard extends StatelessWidget {
+ const TodayCard({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ /// item
+ Widget item({
+ required String title,
+ required String value,
+ required IconData icon,
+ required Color color,
+ }) {
+ return Expanded(
+ child: Container(
+ padding: EdgeInsets.all(10),
+ decoration: BoxDecoration(
+ color: color.withValues(alpha: 0.2),
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Row(
+ spacing: 10,
+ children: [
+ Container(
+ width: 45,
+ height: 45,
+ decoration: BoxDecoration(
+ color: color,
+ borderRadius: BorderRadius.circular(10),
+ ),
+ child: Icon(
+ icon,
+ color: Colors.white,
+ ),
+ ),
+ Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(title, style: Theme.of(context).textTheme.labelLarge),
+ Text(value),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ return GCard(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ spacing: 10,
+ children: [
+ Text("高三数学冲刺班"),
+ Tag(text: "待开始"),
+ ],
+ ),
+ Container(
+ margin: EdgeInsets.only(top: 5),
+ child: Text(
+ "和学生们一起专注学习、共同进步",
+ style: Theme.of(context).textTheme.labelMedium,
+ ),
+ ),
+ ],
+ ),
+ ),
+ Icon(RemixIcons.arrow_right_s_line, size: 30),
+ ],
+ ),
+ Container(
+ margin: EdgeInsets.only(top: 30),
+ child: Row(
+ spacing: 15,
+ children: [
+ item(
+ title: "开始时间",
+ value: "14:00",
+ icon: RemixIcons.time_line,
+ color: Color(0xff2b7efd),
+ ),
+ item(
+ title: "学生人数",
+ value: "8 名",
+ icon: RemixIcons.group_line,
+ color: Color(0xff00c74f),
+ ),
+ item(
+ title: "时长",
+ value: "120 分钟",
+ icon: RemixIcons.book_open_line,
+ color: Color(0xffac45fd),
+ ),
+ ],
+ ),
+ ),
+ Container(
+ margin: EdgeInsets.only(top: 30),
+ height: 45,
+ child: Button(
+ text: "开始自习室",
+ type: ThemeType.success,
+ onPressed: () {
+ context.push(RoutePaths.tRoom);
+ },
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/pages/teacher/room/controls/top_bar.dart b/lib/pages/teacher/room/controls/top_bar.dart
new file mode 100644
index 0000000..6237ac6
--- /dev/null
+++ b/lib/pages/teacher/room/controls/top_bar.dart
@@ -0,0 +1,74 @@
+import 'package:flutter/material.dart';
+import 'package:remixicon/remixicon.dart';
+
+class TopBar extends StatelessWidget implements PreferredSizeWidget {
+ const TopBar({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ //标题子显示内容
+ Widget infoItem({required String title, required IconData icon}) {
+ return Row(
+ spacing: 4,
+ children: [
+ Icon(icon, color: Colors.white54, size: 14),
+ Text(
+ title,
+ style: TextStyle(fontSize: 12, color: Colors.white54),
+ ),
+ ],
+ );
+ }
+
+ //操作按钮
+ Widget actionButton({required IconData icon, required String title}) {
+ return Container(
+ padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ margin: EdgeInsets.only(right: 15),
+ decoration: BoxDecoration(
+ color: Color(0xff4a4f4f),
+ borderRadius: BorderRadius.circular(8),
+ ),
+ child: Row(
+ spacing: 8,
+ children: [
+ Icon(icon, size: 16),
+ Text(title, style: TextStyle(fontSize: 14)),
+ ],
+ ),
+ );
+ }
+
+ return AppBar(
+ backgroundColor: Color(0xff373c3e),
+ foregroundColor: Colors.white,
+ title: Column(
+ spacing: 5,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text("高三数学重置版", style: TextStyle(color: Colors.white, fontSize: 18)),
+ Row(
+ spacing: 15,
+ children: [
+ infoItem(title: "剩余 1小时23分钟", icon: RemixIcons.time_line),
+ infoItem(title: "8 名学生", icon: RemixIcons.group_line),
+ ],
+ ),
+ ],
+ ),
+ actions: [
+ actionButton(
+ icon: RemixIcons.video_on_ai_line,
+ title: "关闭全部",
+ ),
+ actionButton(
+ icon: RemixIcons.volume_up_line,
+ title: "全部静音",
+ ),
+ ],
+ );
+ }
+
+ @override
+ Size get preferredSize => const Size.fromHeight(kToolbarHeight);
+}
diff --git a/lib/pages/teacher/room/t_room_page.dart b/lib/pages/teacher/room/t_room_page.dart
new file mode 100644
index 0000000..4c54aee
--- /dev/null
+++ b/lib/pages/teacher/room/t_room_page.dart
@@ -0,0 +1,46 @@
+import 'package:flutter/material.dart';
+
+import 'controls/top_bar.dart';
+import 'view/student_item.dart';
+import 'view/waiting_start.dart';
+
+class TRoomPage extends StatefulWidget {
+ const TRoomPage({super.key});
+
+ @override
+ State createState() => _TRoomPageState();
+}
+
+class _TRoomPageState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Color(0xff2c3032),
+ appBar: TopBar(),
+ body: true ? WaitingStart() : Padding(
+ padding: const EdgeInsets.all(10),
+ child: Row(
+ spacing: 15,
+ children: [
+ Expanded(
+ child: StudentItem(),
+ ),
+ SizedBox(
+ width: 300,
+ child: ListView.separated(
+ itemBuilder: (_, index) {
+ return SizedBox(
+ height: 250,
+ child: StudentItem(),
+ );
+ },
+ separatorBuilder: (_, __) => SizedBox(height: 15),
+ itemCount: 7,
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/teacher/room/view/student_item.dart b/lib/pages/teacher/room/view/student_item.dart
new file mode 100644
index 0000000..dea852d
--- /dev/null
+++ b/lib/pages/teacher/room/view/student_item.dart
@@ -0,0 +1,105 @@
+import 'package:app/widgets/room/file_drawer.dart';
+import 'package:app/widgets/room/video_surface.dart';
+import 'package:flutter/material.dart';
+import 'package:remixicon/remixicon.dart';
+
+class StudentItem extends StatefulWidget {
+ const StudentItem({super.key});
+
+ @override
+ State createState() => _StudentItemState();
+}
+
+class _StudentItemState extends State {
+ ///打开文件列表
+ void _openFileList(){
+ showFileDialog(context,isUpload: false);
+ }
+ @override
+ Widget build(BuildContext context) {
+ return ClipRRect(
+ borderRadius: BorderRadius.circular(10),
+ child: Container(
+ color: Color(0xFF404649),
+ child: Column(
+ children: [
+ Expanded(
+ child: SizedBox(
+ width: double.infinity,
+ child: Stack(
+ children: [
+ VideoSurface(
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ child: Container(
+ padding: EdgeInsets.symmetric(horizontal: 10, vertical: 8),
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ colors: [Color(0x0b050505), Color(0x54050505)],
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ ),
+ ),
+ child: Text(
+ "李明辉",
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 14,
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ColoredBox(
+ color: Color(0xFF232426),
+ child: Row(
+ children: [
+ _actionItem(icon: RemixIcons.video_on_fill, isActive: false),
+ _actionItem(
+ icon: RemixIcons.mic_off_fill,
+ ),
+ _actionItem(
+ icon: RemixIcons.volume_mute_fill,
+ ),
+ _actionItem(
+ icon: RemixIcons.file_list_3_fill,
+ onTap: _openFileList
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ ///
+ Widget _actionItem({
+ required IconData icon,
+ bool isActive = true,
+ void Function()? onTap,
+ }) {
+ Color offColor = Color(0xFFE75B61);
+ return Expanded(
+ child: Container(
+ height: 50,
+ color: isActive ? null : offColor.withValues(alpha: 0.3),
+ child: IconButton(
+ onPressed: onTap,
+ icon: Icon(
+ icon,
+ color: isActive ? Colors.white : offColor,
+ size: 20,
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/teacher/room/view/waiting_start.dart b/lib/pages/teacher/room/view/waiting_start.dart
new file mode 100644
index 0000000..856292d
--- /dev/null
+++ b/lib/pages/teacher/room/view/waiting_start.dart
@@ -0,0 +1,80 @@
+import 'dart:async';
+
+import 'package:app/utils/time.dart';
+import 'package:flutter/material.dart';
+
+class WaitingStart extends StatefulWidget {
+ const WaitingStart({super.key});
+
+ @override
+ State createState() => _WaitingStartState();
+}
+
+class _WaitingStartState extends State {
+ ///剩余秒
+ int _seconds = 0;
+ Timer? _timer;
+
+ @override
+ void initState() {
+ super.initState();
+ startCountDown();
+ }
+
+ @override
+ void dispose() {
+ super.dispose();
+ _timer?.cancel();
+ _timer = null;
+ }
+
+ ///开始倒计时
+ void startCountDown() {
+ //当前时间
+ DateTime now = DateTime.now();
+ //远端时间
+ DateTime remote = DateTime.parse("2025-11-19 17:10:00".replaceFirst(' ', 'T'));
+ setState(() {
+ _seconds = remote.difference(now).inSeconds;
+ });
+ _timer = Timer.periodic(Duration(seconds: 1), (timer) {
+ setState(() {
+ _seconds--;
+ });
+ if (_seconds <= 0) {
+ _timer?.cancel();
+ _timer = null;
+ _start();
+ }
+ });
+ }
+
+ ///倒计时结束开始
+ void _start() {}
+
+ @override
+ Widget build(BuildContext context) {
+ return Align(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ "未到开播时间,到点后自动开播",
+ style: TextStyle(color: Colors.white),
+ ),
+ Container(
+ margin: EdgeInsets.symmetric(vertical: 10),
+ child: Text(
+ formatSeconds(_seconds),
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 26,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/providers/user_store.dart b/lib/providers/user_store.dart
new file mode 100644
index 0000000..e69de29
diff --git a/lib/request/dto/base_dto.dart b/lib/request/dto/base_dto.dart
new file mode 100644
index 0000000..76e11ca
--- /dev/null
+++ b/lib/request/dto/base_dto.dart
@@ -0,0 +1,20 @@
+class ApiDto {
+ final int code;
+ final String message;
+ final T data;
+
+ ApiDto({
+ required this.code,
+ required this.message,
+ required this.data
+ });
+
+
+ factory ApiDto.fromJson(Map json) {
+ return ApiDto(
+ code: json['code'],
+ message: json['message'],
+ data: json['data'],
+ );
+ }
+}
diff --git a/lib/request/network/interceptor.dart b/lib/request/network/interceptor.dart
new file mode 100644
index 0000000..5e7892e
--- /dev/null
+++ b/lib/request/network/interceptor.dart
@@ -0,0 +1,51 @@
+import 'package:dio/dio.dart';
+
+import '../dto/base_dto.dart';
+
+
+///请求拦截器
+void onRequest(
+ RequestOptions options,
+ RequestInterceptorHandler handler,
+) {
+ return handler.next(options);
+}
+
+///响应拦截器
+void onResponse(
+ Response response,
+ ResponseInterceptorHandler handler,
+) {
+ var apiResponse = ApiDto.fromJson(response.data);
+ if (apiResponse.code == 1) {
+ handler.next(response);
+ } else {
+ handler.reject(
+ DioException(
+ requestOptions: response.requestOptions,
+ response: response,
+ error: {'code': 0, 'message': apiResponse.message},
+ ),
+ );
+ }
+}
+
+///错误响应
+void onError(
+ DioException e,
+ ErrorInterceptorHandler handler,
+) {
+ if (e.type == DioExceptionType.connectionTimeout) {
+ print("请求超时");
+ } else if (e.type == DioExceptionType.badResponse) {
+ if (e.response?.statusCode == 404) {
+ print("接口404不存在");
+ } else {
+ print("500");
+ }
+ } else if (e.type == DioExceptionType.connectionError) {
+ print("网络连接失败");
+ } else {
+ print("接口请求异常报错");
+ }
+}
diff --git a/lib/request/network/request.dart b/lib/request/network/request.dart
new file mode 100644
index 0000000..fb9f9f5
--- /dev/null
+++ b/lib/request/network/request.dart
@@ -0,0 +1,43 @@
+import 'package:dio/dio.dart';
+import 'package:app/config/config.dart';
+
+import 'interceptor.dart';
+
+class Request {
+ static Dio _dio = Dio();
+
+ //返回单例
+ factory Request() {
+ return Request._instance();
+ }
+
+ //初始化
+ Request._instance() {
+ //创建基本配置
+ final BaseOptions options = BaseOptions(
+ baseUrl: Config.baseUrl(),
+ connectTimeout: const Duration(seconds: 10),
+ receiveTimeout: const Duration(seconds: 10),
+ );
+
+ _dio = Dio(options);
+
+ _dio.interceptors.add(InterceptorsWrapper(
+ onRequest: onRequest,
+ onResponse: onResponse,
+ onError: onError,
+ ));
+ }
+
+ ///get请求
+ Future get(String path, [Map? params]) async {
+ var res = await _dio.get(path, queryParameters: params);
+ return res.data;
+ }
+
+ ///post请求
+ Future post(String path, Object? data) async {
+ var res = await _dio.post(path, data: data);
+ return res.data;
+ }
+}
diff --git a/lib/router/modules/common_routes.dart b/lib/router/modules/common_routes.dart
new file mode 100644
index 0000000..928a9db
--- /dev/null
+++ b/lib/router/modules/common_routes.dart
@@ -0,0 +1,20 @@
+import 'package:app/pages/common/auth/login_page.dart';
+import 'package:app/pages/common/splash/splash_page.dart';
+import 'package:app/router/router_config.dart';
+
+import '../route_paths.dart';
+
+List commonRoutes = [
+ RouterConfig(
+ path: RoutePaths.splash,
+ child: (state) {
+ return SplashPage();
+ },
+ ),
+ RouterConfig(
+ path: RoutePaths.login,
+ child: (state) {
+ return LoginPage();
+ },
+ ),
+];
diff --git a/lib/router/modules/student_routes.dart b/lib/router/modules/student_routes.dart
new file mode 100644
index 0000000..3c5715d
--- /dev/null
+++ b/lib/router/modules/student_routes.dart
@@ -0,0 +1,20 @@
+import 'package:app/pages/student/home/s_home_page.dart';
+import 'package:app/router/router_config.dart';
+
+import '../../pages/student/room/s_room_page.dart';
+import '../route_paths.dart';
+
+List studentRoutes = [
+ RouterConfig(
+ path: RoutePaths.sHome,
+ child: (state) {
+ return SHomePage();
+ },
+ ),
+ RouterConfig(
+ path: RoutePaths.sRoom,
+ child: (state) {
+ return SRoomPage();
+ },
+ ),
+];
\ No newline at end of file
diff --git a/lib/router/modules/teacher_routes.dart b/lib/router/modules/teacher_routes.dart
new file mode 100644
index 0000000..01ca542
--- /dev/null
+++ b/lib/router/modules/teacher_routes.dart
@@ -0,0 +1,20 @@
+import 'package:app/pages/teacher/home/t_home_page.dart';
+import 'package:app/pages/teacher/room/t_room_page.dart';
+import 'package:app/router/router_config.dart';
+
+import '../route_paths.dart';
+
+List teacherRoutes = [
+ RouterConfig(
+ path: RoutePaths.tHome,
+ child: (state) {
+ return THomePage();
+ },
+ ),
+ RouterConfig(
+ path: RoutePaths.tRoom,
+ child: (state) {
+ return TRoomPage();
+ },
+ ),
+];
\ No newline at end of file
diff --git a/lib/router/route_paths.dart b/lib/router/route_paths.dart
new file mode 100644
index 0000000..f8cdffb
--- /dev/null
+++ b/lib/router/route_paths.dart
@@ -0,0 +1,20 @@
+class RoutePaths {
+ RoutePaths._();
+
+ ///闪烁页
+ static const splash = "/";
+
+ ///登录
+ static const login = "/login";
+
+ ///协议
+ static const agreement = "/agreement";
+
+ ///老师端、学生端首页
+ static const tHome = "/t/home";
+ static const sHome = "/s/home";
+
+ ///老师端、学生端自习室
+ static const tRoom = "/t/study_room";
+ static const sRoom = "/s/study_room";
+}
diff --git a/lib/router/router_config.dart b/lib/router/router_config.dart
new file mode 100644
index 0000000..b52f655
--- /dev/null
+++ b/lib/router/router_config.dart
@@ -0,0 +1,16 @@
+import 'dart:async';
+
+import 'package:flutter/cupertino.dart';
+import 'package:go_router/go_router.dart';
+
+class RouterConfig {
+ String path;
+ FutureOr Function(BuildContext, GoRouterState)? redirect;
+ Widget Function(GoRouterState) child;
+
+ RouterConfig({
+ required this.path,
+ required this.child,
+ this.redirect,
+ });
+}
diff --git a/lib/router/routes.dart b/lib/router/routes.dart
new file mode 100644
index 0000000..a242534
--- /dev/null
+++ b/lib/router/routes.dart
@@ -0,0 +1,28 @@
+import 'package:app/router/modules/common_routes.dart';
+import 'package:app/router/modules/student_routes.dart';
+import 'package:app/router/modules/teacher_routes.dart';
+import 'package:app/router/route_paths.dart';
+import 'package:app/router/router_config.dart';
+import 'package:go_router/go_router.dart';
+
+List routeConfigs = [
+ ...commonRoutes,
+ ...teacherRoutes,
+ ...studentRoutes
+];
+
+//for循环遍历
+List routes = routeConfigs.map((item) {
+ return GoRoute(
+ path: item.path,
+ builder: (context, state) {
+ return item.child(state);
+ },
+ );
+}).toList();
+
+//变量命名
+GoRouter goRouter = GoRouter(
+ initialLocation: RoutePaths.tHome,
+ routes: routes,
+);
diff --git a/lib/utils/time.dart b/lib/utils/time.dart
new file mode 100644
index 0000000..5d29ad6
--- /dev/null
+++ b/lib/utils/time.dart
@@ -0,0 +1,48 @@
+/// 格式化时间
+String formatDate(dynamic date, [String format = 'YYYY-MM-DD hh:mm:ss']) {
+ DateTime dateTime;
+
+ if (date is String) {
+ // 如果是字符串类型,尝试将其解析为 DateTime
+ dateTime = DateTime.tryParse(date) ?? DateTime.now();
+ } else if (date is DateTime) {
+ // 如果是 DateTime 类型,直接使用
+ dateTime = date;
+ } else {
+ // 如果不是合法的输入类型,默认使用当前时间
+ dateTime = DateTime.now();
+ }
+
+ final yyyy = dateTime.year.toString();
+ final MM = (dateTime.month).toString().padLeft(2, '0');
+ final dd = (dateTime.day).toString().padLeft(2, '0');
+ final HH = (dateTime.hour).toString().padLeft(2, '0');
+ final mm = (dateTime.minute).toString().padLeft(2, '0');
+ final ss = (dateTime.second).toString().padLeft(2, '0');
+
+ String result = format
+ .replaceFirst(RegExp('YYYY'), '$yyyy')
+ .replaceFirst(RegExp('MM'), MM)
+ .replaceFirst(RegExp('DD'), dd)
+ .replaceFirst(RegExp('hh'), HH)
+ .replaceFirst(RegExp('mm'), mm)
+ .replaceFirst(RegExp('ss'), ss);
+
+ return result;
+}
+
+/// 将秒数格式化为 00:00 或 00:00:00
+/// - [seconds]: 秒数
+String formatSeconds(int seconds) {
+ final h = seconds ~/ 3600;
+ final m = (seconds % 3600) ~/ 60;
+ final s = seconds % 60;
+
+ String twoDigits(int n) => n.toString().padLeft(2, '0');
+
+ if (h > 0) {
+ return '${twoDigits(h)}:${twoDigits(m)}:${twoDigits(s)}';
+ } else {
+ return '${twoDigits(m)}:${twoDigits(s)}';
+ }
+}
diff --git a/lib/widgets/base/button/index.dart b/lib/widgets/base/button/index.dart
new file mode 100644
index 0000000..20b4327
--- /dev/null
+++ b/lib/widgets/base/button/index.dart
@@ -0,0 +1,65 @@
+import 'package:app/config/theme/theme.dart';
+import 'package:flutter/material.dart';
+
+import '../config/config.dart';
+
+class Button extends StatelessWidget {
+ final double? width;
+ final String text;
+ final ThemeType type;
+ final BorderRadius radius;
+ final VoidCallback onPressed;
+ final bool loading;
+ final bool disabled;
+
+ const Button({
+ super.key,
+ this.width,
+ this.radius = const BorderRadius.all(Radius.circular(80)),
+ required this.text,
+ required this.onPressed,
+ this.type = ThemeType.primary,
+ this.loading = false,
+ this.disabled = false,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ final bgDecoration = switch (type) {
+ ThemeType.primary => BoxDecoration(color: Theme.of(context).primaryColor),
+ ThemeType.success => BoxDecoration(color: context.success),
+ ThemeType.danger => BoxDecoration(color: context.danger),
+ ThemeType.warning => BoxDecoration(color: context.warning),
+ ThemeType.info => BoxDecoration(color: context.info),
+ };
+
+ return Opacity(
+ opacity: disabled ? 0.5 : 1,
+ child: Container(
+ width: width,
+ decoration: bgDecoration.copyWith(borderRadius: radius),
+ child: Material(
+ type: MaterialType.transparency, // 让波纹依附在上层容器
+ child: InkWell(
+ borderRadius: radius,
+ onTap: disabled || loading ? null : onPressed,
+ splashColor: Colors.white.withValues(alpha: 0.2),
+ highlightColor: Colors.white.withValues(alpha: 0.2),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 7),
+ child: Center(
+ child: Text(
+ text,
+ style: TextStyle(
+ color: type != ThemeType.info ? Colors.white : Colors.black,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/base/card/g_card.dart b/lib/widgets/base/card/g_card.dart
new file mode 100644
index 0000000..99e4ba5
--- /dev/null
+++ b/lib/widgets/base/card/g_card.dart
@@ -0,0 +1,27 @@
+import 'package:app/config/theme/base/app_theme_ext.dart';
+import 'package:flutter/material.dart';
+
+class GCard extends StatelessWidget {
+ final Widget child;
+
+ const GCard({super.key, required this.child});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ padding: EdgeInsets.all(context.pagePadding),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
+ borderRadius: BorderRadius.circular(8),
+ boxShadow: [
+ BoxShadow(
+ color: Theme.of(context).colorScheme.shadow,
+ blurRadius: 9,
+ offset: const Offset(0, 4),
+ ),
+ ],
+ ),
+ child: child,
+ );
+ }
+}
diff --git a/lib/widgets/base/config/color.dart b/lib/widgets/base/config/color.dart
new file mode 100644
index 0000000..3b45131
--- /dev/null
+++ b/lib/widgets/base/config/color.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+
+import 'config.dart';
+
+class ColorResolver {
+ final Color bg;
+ final Color font;
+ final Color border;
+
+ ColorResolver(this.bg, this.font, this.border);
+}
+
+///返回颜色
+ColorResolver resolveEffectColors(Color color, Effect effect) => switch (effect) {
+ Effect.dark => ColorResolver(color, Colors.white, color),
+ Effect.light => () {
+ final pale = color.withValues(alpha: 0.2);
+ return ColorResolver(pale, color, pale);
+ }(), // 注意这里多了 (),表示立即调用
+ Effect.plain => ColorResolver(Colors.white, color, color),
+};
diff --git a/lib/widgets/base/config/config.dart b/lib/widgets/base/config/config.dart
new file mode 100644
index 0000000..559ec1c
--- /dev/null
+++ b/lib/widgets/base/config/config.dart
@@ -0,0 +1,15 @@
+///主题风格
+enum Effect {
+ dark,
+ light,
+ plain,
+}
+
+///主题类型
+enum ThemeType {
+ primary,
+ success,
+ warning,
+ danger,
+ info,
+}
diff --git a/lib/widgets/base/tag/index.dart b/lib/widgets/base/tag/index.dart
new file mode 100644
index 0000000..a4e4b52
--- /dev/null
+++ b/lib/widgets/base/tag/index.dart
@@ -0,0 +1,58 @@
+import 'package:app/config/theme/base/app_theme_ext.dart';
+import 'package:flutter/material.dart';
+
+import '../config/color.dart';
+import '../config/config.dart';
+
+class Tag extends StatelessWidget {
+ final String text;
+ final Color? color;
+ final ThemeType type;
+ final Effect effect;
+ final EdgeInsets padding;
+
+ const Tag({
+ super.key,
+ required this.text,
+ this.color,
+ this.type = ThemeType.primary,
+ this.effect = Effect.dark,
+ this.padding = const EdgeInsets.symmetric(vertical: 3, horizontal: 6),
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ ///颜色
+ var baseColor = switch (type) {
+ ThemeType.primary => Theme.of(context).primaryColor,
+ ThemeType.success => context.success,
+ ThemeType.warning => context.warning,
+ ThemeType.danger => context.danger,
+ ThemeType.info => context.info,
+ };
+ if (color != null) {
+ baseColor = color!;
+ }
+
+ ColorResolver colorResolver = resolveEffectColors(baseColor, effect);
+
+ return Container(
+ padding: padding,
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(5),
+ color: colorResolver.bg,
+ border: Border.all(
+ color: colorResolver.border,
+ width: 1,
+ ),
+ ),
+ child: Text(
+ text,
+ style: TextStyle(
+ color: colorResolver.font,
+ fontSize: 12,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/base/transition/slide_hide.dart b/lib/widgets/base/transition/slide_hide.dart
new file mode 100644
index 0000000..d21ea1c
--- /dev/null
+++ b/lib/widgets/base/transition/slide_hide.dart
@@ -0,0 +1,70 @@
+import 'package:flutter/material.dart';
+
+enum SlideDirection { up, down }
+
+class SlideHide extends StatefulWidget {
+ final Widget child;
+ final bool hide; // 是否隐藏
+ final SlideDirection direction;
+ final Duration duration;
+
+ const SlideHide({
+ super.key,
+ required this.child,
+ this.hide = false,
+ this.direction = SlideDirection.up,
+ this.duration = const Duration(milliseconds: 200),
+ });
+
+ @override
+ State createState() => _SlideHideState();
+}
+
+class _SlideHideState extends State with SingleTickerProviderStateMixin {
+ late final AnimationController _controller;
+ late final Animation _animation;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = AnimationController(
+ vsync: this,
+ duration: widget.duration,
+ );
+ _animation = Tween(
+ begin: Offset.zero,
+ end: widget.direction == SlideDirection.up ? const Offset(0, -1) : const Offset(0, 1),
+ ).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
+
+ if (widget.hide) {
+ _controller.value = 1;
+ }
+ }
+
+ @override
+ void didUpdateWidget(covariant SlideHide oldWidget) {
+ super.didUpdateWidget(oldWidget);
+ if (oldWidget.hide != widget.hide) {
+ if (widget.hide) {
+ _controller.forward();
+ } else {
+ _controller.reverse();
+ }
+ }
+ }
+
+
+ @override
+ void dispose() {
+ _controller.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return SlideTransition(
+ position: _animation,
+ child: widget.child,
+ );
+ }
+}
diff --git a/lib/widgets/common/preview/file_previewer.dart b/lib/widgets/common/preview/file_previewer.dart
new file mode 100644
index 0000000..cf7e243
--- /dev/null
+++ b/lib/widgets/common/preview/file_previewer.dart
@@ -0,0 +1,61 @@
+import 'package:cached_network_image/cached_network_image.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_cached_pdfview/flutter_cached_pdfview.dart';
+import 'package:go_router/go_router.dart';
+
+void showFilePreviewer(
+ BuildContext context, {
+ required String url,
+}) {
+ showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return FilePreviewer(
+ url: url,
+ );
+ },
+ );
+}
+
+class FilePreviewer extends StatelessWidget {
+ final String url;
+
+ const FilePreviewer({super.key, this.url = ""});
+
+ bool _isImage(String suffix) {
+ final lower = suffix.toLowerCase();
+ return ['jpg', 'jpeg', 'png', 'gif', 'webp'].contains(lower);
+ }
+
+ bool _isPdf(String suffix) {
+ return suffix.toLowerCase() == 'pdf';
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final suffix = url.split('.').last;
+ Widget child;
+
+ if (_isImage(suffix)) {
+ child = InteractiveViewer(
+ child: CachedNetworkImage(
+ imageUrl: url,
+ ),
+ );
+ } else if (_isPdf(suffix)) {
+ child = PDF(
+ enableSwipe: true,
+ ).cachedFromUrl(url);
+ } else {
+ child = const Text('不支持的文件类型');
+ }
+ return GestureDetector(
+ onTap: () {
+ context.pop();
+ },
+ child: Container(
+ child: child,
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/room/file_drawer.dart b/lib/widgets/room/file_drawer.dart
new file mode 100644
index 0000000..ace88f1
--- /dev/null
+++ b/lib/widgets/room/file_drawer.dart
@@ -0,0 +1,95 @@
+import 'package:app/config/theme/base/app_theme_ext.dart';
+import 'package:app/widgets/base/button/index.dart';
+import 'package:flutter/material.dart';
+
+import '../common/preview/file_previewer.dart';
+
+///快捷打开文件弹窗
+void showFileDialog(
+ BuildContext context, {
+ bool isUpload = true,
+}) {
+ showGeneralDialog(
+ context: context,
+ barrierDismissible: true,
+ // 点击外部关闭
+ barrierLabel: "RightSheet",
+ pageBuilder: (context, animation, secondaryAnimation) {
+ return FileDrawer(
+ isUpload: isUpload,
+ );
+ },
+ transitionBuilder: (context, animation, secondaryAnimation, child) {
+ final tween = Tween(begin: Offset(1, 0), end: Offset(0, 0));
+ return SlideTransition(
+ position: tween.animate(animation),
+ child: child,
+ );
+ },
+ );
+}
+
+///文件弹窗
+class FileDrawer extends StatefulWidget {
+ final bool isUpload;
+
+ const FileDrawer({super.key, this.isUpload = true});
+
+ @override
+ State createState() => _FileDrawerState();
+}
+
+class _FileDrawerState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Align(
+ alignment: Alignment.centerRight,
+ child: Container(
+ width: 300,
+ color: Colors.white,
+ padding: EdgeInsets.all(context.pagePadding),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ '上传文件列表',
+ style: Theme.of(context).textTheme.titleSmall,
+ ),
+ Expanded(
+ child: ListView.separated(
+ padding: EdgeInsets.symmetric(vertical: 15),
+ itemBuilder: (_, index) {
+ return InkWell(
+ onTap: () {
+ showFilePreviewer(
+ context,
+ url: "https://doaf.asia/api/assets/1/图/65252305_p0.jpg",
+ );
+ },
+ child: Container(
+ padding: EdgeInsets.symmetric(vertical: 10, horizontal: 10),
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surfaceContainer,
+ borderRadius: BorderRadius.circular(5),
+ ),
+ child: Text("文件1.png", style: TextStyle(fontSize: 14)),
+ ),
+ );
+ },
+ separatorBuilder: (_, __) => SizedBox(height: 15),
+ itemCount: 15,
+ ),
+ ),
+ Visibility(
+ visible: widget.isUpload,
+ child: Button(
+ text: "上传",
+ onPressed: () {},
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/widgets/room/video_surface.dart b/lib/widgets/room/video_surface.dart
new file mode 100644
index 0000000..7631da1
--- /dev/null
+++ b/lib/widgets/room/video_surface.dart
@@ -0,0 +1,43 @@
+import 'package:flutter/material.dart';
+
+/// 视频画面显示状态
+enum VideoState {
+ /// 正常显示视频
+ normal,
+
+ /// 摄像头关闭
+ closed,
+
+ /// 掉线 / 未连接
+ offline,
+
+ /// 加载中(进房、拉流等)
+ loading,
+
+ /// 错误状态(拉流失败等)
+ error,
+}
+
+class VideoSurface extends StatelessWidget {
+ final VideoState state;
+
+ const VideoSurface({super.key, this.state = VideoState.normal});
+
+ @override
+ Widget build(BuildContext context) {
+ String stateText = switch (state) {
+ VideoState.closed => "摄像头已关闭",
+ VideoState.offline => "掉线",
+ VideoState.loading => "加载中",
+ VideoState.error => "错误",
+ _ => "未知",
+ };
+ //如果不是正常
+ if (state != VideoState.normal) {
+ return Align(
+ child: Text(stateText, style: TextStyle(color: Colors.white70)),
+ );
+ }
+ return Container();
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
new file mode 100644
index 0000000..1a12a5b
--- /dev/null
+++ b/pubspec.lock
@@ -0,0 +1,711 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.13.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.2"
+ cached_network_image:
+ dependency: "direct main"
+ description:
+ name: cached_network_image
+ sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.4.1"
+ cached_network_image_platform_interface:
+ dependency: transitive
+ description:
+ name: cached_network_image_platform_interface
+ sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.1.1"
+ cached_network_image_web:
+ dependency: transitive
+ description:
+ name: cached_network_image_web
+ sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.3.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.0"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.2"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.19.1"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.7"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.0.8"
+ dio:
+ dependency: "direct main"
+ description:
+ name: dio
+ sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.9.0"
+ dio_web_adapter:
+ dependency: transitive
+ description:
+ name: dio_web_adapter
+ sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.1"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.3.3"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.4"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "7.0.1"
+ fixnum:
+ dependency: transitive
+ description:
+ name: fixnum
+ sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.1"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_cache_manager:
+ dependency: transitive
+ description:
+ name: flutter_cache_manager
+ sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.4.1"
+ flutter_cached_pdfview:
+ dependency: "direct main"
+ description:
+ name: flutter_cached_pdfview
+ sha256: b1a3dc1cca0ac6b35bc95aeb8253208e211468eadd68829bae8f2696f3601b5a
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.4.3"
+ flutter_easyloading:
+ dependency: "direct main"
+ description:
+ name: flutter_easyloading
+ sha256: ba21a3c883544e582f9cc455a4a0907556714e1e9cf0eababfcb600da191d17c
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.5"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.0.0"
+ flutter_localizations:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_pdfview:
+ dependency: transitive
+ description:
+ name: flutter_pdfview
+ sha256: c0b2cc4ebf461a5a4bb9312a165222475a7d93845c7a0703f4abb7f442eb6d54
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.3"
+ flutter_screenutil:
+ dependency: "direct main"
+ description:
+ name: flutter_screenutil
+ sha256: "8239210dd68bee6b0577aa4a090890342d04a136ce1c81f98ee513fc0ce891de"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.9.3"
+ flutter_spinkit:
+ dependency: transitive
+ description:
+ name: flutter_spinkit
+ sha256: "77850df57c00dc218bfe96071d576a8babec24cf58b2ed121c83cca4a2fdce7f"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.2.2"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ go_router:
+ dependency: "direct main"
+ description:
+ name: go_router
+ sha256: d8f590a69729f719177ea68eb1e598295e8dbc41bbc247fed78b2c8a25660d7c
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "16.3.0"
+ http:
+ dependency: transitive
+ description:
+ name: http
+ sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.6.0"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.1.2"
+ intl:
+ dependency: "direct main"
+ description:
+ name: intl
+ sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.20.2"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "10.0.9"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.9"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.0.1"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.1.1"
+ logger:
+ dependency: "direct main"
+ description:
+ name: logger
+ sha256: a7967e31b703831a893bbc3c3dd11db08126fe5f369b5c648a36f821979f5be3
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.6.2"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.3.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.12.17"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.11.1"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.16.0"
+ mime:
+ dependency: transitive
+ description:
+ name: mime
+ sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.0.0"
+ nested:
+ dependency: transitive
+ description:
+ name: nested
+ sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.0.0"
+ octo_image:
+ dependency: transitive
+ description:
+ name: octo_image
+ sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.0"
+ package_info_plus:
+ dependency: "direct main"
+ description:
+ name: package_info_plus
+ sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "8.3.1"
+ package_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: package_info_plus_platform_interface
+ sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.2.1"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.9.1"
+ path_provider:
+ dependency: transitive
+ description:
+ name: path_provider
+ sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.5"
+ path_provider_android:
+ dependency: transitive
+ description:
+ name: path_provider_android
+ sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.2.19"
+ path_provider_foundation:
+ dependency: transitive
+ description:
+ name: path_provider_foundation
+ sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.2"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.2.1"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.2"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.3.0"
+ permission_handler:
+ dependency: "direct main"
+ description:
+ name: permission_handler
+ sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "12.0.1"
+ permission_handler_android:
+ dependency: transitive
+ description:
+ name: permission_handler_android
+ sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "13.0.1"
+ permission_handler_apple:
+ dependency: transitive
+ description:
+ name: permission_handler_apple
+ sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "9.4.7"
+ permission_handler_html:
+ dependency: transitive
+ description:
+ name: permission_handler_html
+ sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.1.3+5"
+ permission_handler_platform_interface:
+ dependency: transitive
+ description:
+ name: permission_handler_platform_interface
+ sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.3.0"
+ permission_handler_windows:
+ dependency: transitive
+ description:
+ name: permission_handler_windows
+ sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.2.1"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.1.6"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.8"
+ provider:
+ dependency: "direct main"
+ description:
+ name: provider
+ sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "6.1.5+1"
+ remixicon:
+ dependency: "direct main"
+ description:
+ name: remixicon
+ sha256: "9a0e6a67d622a1d6ddc5e5cff4898f9711ec8880fc2ebeb720f6e0bc268ea905"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.1"
+ rxdart:
+ dependency: transitive
+ description:
+ name: rxdart
+ sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.28.0"
+ shared_preferences:
+ dependency: "direct main"
+ description:
+ name: shared_preferences
+ sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.5.3"
+ shared_preferences_android:
+ dependency: transitive
+ description:
+ name: shared_preferences_android
+ sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.13"
+ shared_preferences_foundation:
+ dependency: transitive
+ description:
+ name: shared_preferences_foundation
+ sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.5.4"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_platform_interface:
+ dependency: transitive
+ description:
+ name: shared_preferences_platform_interface
+ sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.1"
+ shared_preferences_web:
+ dependency: transitive
+ description:
+ name: shared_preferences_web
+ sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.3"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.1"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.10.1"
+ sqflite:
+ dependency: transitive
+ description:
+ name: sqflite
+ sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.2"
+ sqflite_android:
+ dependency: transitive
+ description:
+ name: sqflite_android
+ sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.1"
+ sqflite_common:
+ dependency: transitive
+ description:
+ name: sqflite_common
+ sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.5.6"
+ sqflite_darwin:
+ dependency: transitive
+ description:
+ name: sqflite_darwin
+ sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.2"
+ sqflite_platform_interface:
+ dependency: transitive
+ description:
+ name: sqflite_platform_interface
+ sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.4.0"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.12.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.4"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.1"
+ synchronized:
+ dependency: transitive
+ description:
+ name: synchronized
+ sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "3.4.0"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.2.2"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "0.7.4"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.4.0"
+ uuid:
+ dependency: transitive
+ description:
+ name: uuid
+ sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "4.5.2"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "2.1.4"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "15.0.0"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.1"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "5.15.0"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
+ url: "https://pub.flutter-io.cn"
+ source: hosted
+ version: "1.1.0"
+sdks:
+ dart: ">=3.8.1 <4.0.0"
+ flutter: ">=3.29.0"
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..4fc3551
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,37 @@
+name: app
+description: "A new Flutter project."
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+version: 1.0.0+1
+
+environment:
+ sdk: ^3.8.1
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_localizations:
+ sdk: flutter
+ intl: any
+ cupertino_icons: ^1.0.8
+ go_router: ^16.2.4
+ permission_handler: ^12.0.0+1
+ provider: ^6.1.5
+ shared_preferences: ^2.5.3
+ dio: ^5.8.0+1
+ package_info_plus: ^8.3.0
+ logger: ^2.6.2
+ flutter_screenutil: ^5.9.3
+ remixicon: ^1.4.1
+ flutter_easyloading: ^3.0.5
+ cached_network_image: ^3.4.1
+ flutter_cached_pdfview: ^0.4.3
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^5.0.0
+
+flutter:
+ uses-material-design: true
+ generate: true
diff --git a/test/widget_test.dart b/test/widget_test.dart
new file mode 100644
index 0000000..4e2a713
--- /dev/null
+++ b/test/widget_test.dart
@@ -0,0 +1,30 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility in the flutter_test package. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:app/main.dart';
+
+void main() {
+ testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that our counter starts at 0.
+ expect(find.text('0'), findsOneWidget);
+ expect(find.text('1'), findsNothing);
+
+ // Tap the '+' icon and trigger a frame.
+ await tester.tap(find.byIcon(Icons.add));
+ await tester.pump();
+
+ // Verify that our counter has incremented.
+ expect(find.text('0'), findsNothing);
+ expect(find.text('1'), findsOneWidget);
+ });
+}