From c0c47fd6397695ac95cc2604de5f81412fc7506c Mon Sep 17 00:00:00 2001 From: zensgit <77236085+zensgit@users.noreply.github.com> Date: Fri, 26 Sep 2025 16:23:03 +0800 Subject: [PATCH 1/5] api: fix owner_id + user insert schema; add route-level e2e tests for register and enhanced register; refresh SQLx cache --- jive-api/src/handlers/auth.rs | 58 +++++++----- jive-api/src/handlers/enhanced_profile.rs | 13 ++- jive-api/src/main.rs | 2 +- jive-api/src/services/family_service.rs | 8 +- .../auth_register_enhanced_route_e2e_test.rs | 59 ++++++++++++ .../auth_register_route_e2e_test.rs | 93 +++++++++++++++++++ jive-api/tests/integration/mod.rs | 1 + 7 files changed, 204 insertions(+), 30 deletions(-) create mode 100644 jive-api/tests/integration/auth_register_enhanced_route_e2e_test.rs create mode 100644 jive-api/tests/integration/auth_register_route_e2e_test.rs diff --git a/jive-api/src/handlers/auth.rs b/jive-api/src/handlers/auth.rs index fac516b9..6329e3f0 100644 --- a/jive-api/src/handlers/auth.rs +++ b/jive-api/src/handlers/auth.rs @@ -124,35 +124,24 @@ pub async fn register( .map_err(|_| ApiError::InternalServerError)? .to_string(); - // 创建用户 + // 创建用户与家庭的 ID let user_id = Uuid::new_v4(); - let family_id = Uuid::new_v4(); // 为新用户创建默认家庭 + let family_id = Uuid::new_v4(); // 开始事务 let mut tx = pool.begin().await .map_err(|e| ApiError::DatabaseError(e.to_string()))?; - // 创建家庭 - sqlx::query( - r#" - INSERT INTO families (id, name, created_at, updated_at) - VALUES ($1, $2, NOW(), NOW()) - "# - ) - .bind(family_id) - .bind(format!("{}'s Family", req.name)) - .execute(&mut *tx) - .await - .map_err(|e| ApiError::DatabaseError(e.to_string()))?; - - // 创建用户(将 name 写入 name 与 full_name,便于后续使用) + // 先创建用户(避免 families.owner_id 外键约束失败) + tracing::info!(target: "auth_register", user_id = %user_id, family_id = %family_id, email = %final_email, "Creating user then family with owner_id"); sqlx::query( r#" INSERT INTO users ( - id, email, username, full_name, password_hash, current_family_id, - status, email_verified, created_at, updated_at + id, email, username, name, full_name, password_hash, + is_active, email_verified, created_at, updated_at ) VALUES ( - $1, $2, $3, $4, $5, $6, 'active', false, NOW(), NOW() + $1, $2, $3, $4, $5, $6, + true, false, NOW(), NOW() ) "# ) @@ -160,26 +149,51 @@ pub async fn register( .bind(&final_email) .bind(&username_opt) .bind(&req.name) + .bind(&req.name) .bind(password_hash) + .execute(&mut *tx) + .await + .map_err(|e| ApiError::DatabaseError(e.to_string()))?; + + // 再创建家庭(带 owner_id) + tracing::info!(target: "auth_register", user_id = %user_id, family_id = %family_id, "Inserting family with owner_id in register"); + sqlx::query( + r#" + INSERT INTO families (id, name, owner_id, created_at, updated_at) + VALUES ($1, $2, $3, NOW(), NOW()) + "# + ) .bind(family_id) + .bind(format!("{}'s Family", req.name)) + .bind(user_id) .execute(&mut *tx) .await .map_err(|e| ApiError::DatabaseError(e.to_string()))?; - // 创建默认账本 + // 创建默认账本(标记 is_default,记录创建者) let ledger_id = Uuid::new_v4(); sqlx::query( r#" - INSERT INTO ledgers (id, family_id, name, currency, created_at, updated_at) - VALUES ($1, $2, '默认账本', 'CNY', NOW(), NOW()) + INSERT INTO ledgers (id, family_id, name, currency, created_by, is_default, created_at, updated_at) + VALUES ($1, $2, '默认账本', 'CNY', $3, true, NOW(), NOW()) "# ) .bind(ledger_id) .bind(family_id) + .bind(user_id) .execute(&mut *tx) .await .map_err(|e| ApiError::DatabaseError(e.to_string()))?; + // 绑定用户的当前家庭并提交事务 + tracing::info!(target: "auth_register", user_id = %user_id, family_id = %family_id, "Binding current_family_id and committing"); + sqlx::query("UPDATE users SET current_family_id = $1 WHERE id = $2") + .bind(family_id) + .bind(user_id) + .execute(&mut *tx) + .await + .map_err(|e| ApiError::DatabaseError(e.to_string()))?; + // 提交事务 tx.commit().await .map_err(|e| ApiError::DatabaseError(e.to_string()))?; diff --git a/jive-api/src/handlers/enhanced_profile.rs b/jive-api/src/handlers/enhanced_profile.rs index 7f818764..fca67848 100644 --- a/jive-api/src/handlers/enhanced_profile.rs +++ b/jive-api/src/handlers/enhanced_profile.rs @@ -147,6 +147,7 @@ pub async fn register_with_preferences( .map_err(|e| ApiError::DatabaseError(e.to_string()))?; // Create family with user's preferences + tracing::info!(target: "enhanced_register", user_id = %user_id, name = %req.name, "Creating family via FamilyService (owner_id)"); let family_service = FamilyService::new(pool.clone()); let family_request = CreateFamilyRequest { name: Some(format!("{}的家庭", req.name)), @@ -155,12 +156,16 @@ pub async fn register_with_preferences( locale: Some(req.language.clone()), }; - let family = family_service - .create_family(user_id, family_request) - .await - .map_err(|_e| ApiError::InternalServerError)?; + let family = match family_service.create_family(user_id, family_request).await { + Ok(f) => f, + Err(e) => { + tracing::error!(target: "enhanced_register", error=?e, user_id=%user_id, "create_family failed"); + return Err(ApiError::InternalServerError); + } + }; // Update user's current family + tracing::info!(target: "enhanced_register", user_id = %user_id, family_id = %family.id, "Binding current_family_id after enhanced register"); sqlx::query("UPDATE users SET current_family_id = $1 WHERE id = $2") .bind(family.id) .bind(user_id) diff --git a/jive-api/src/main.rs b/jive-api/src/main.rs index b02176c6..96f0ddf2 100644 --- a/jive-api/src/main.rs +++ b/jive-api/src/main.rs @@ -278,7 +278,7 @@ async fn main() -> Result<(), Box> { .route("/api/v1/rules/execute", post(execute_rules)) // 认证 API - .route("/api/v1/auth/register", post(auth_handlers::register_with_family)) + .route("/api/v1/auth/register", post(auth_handlers::register)) .route("/api/v1/auth/login", post(auth_handlers::login)) .route("/api/v1/auth/refresh", post(auth_handlers::refresh_token)) .route("/api/v1/auth/user", get(auth_handlers::get_current_user)) diff --git a/jive-api/src/services/family_service.rs b/jive-api/src/services/family_service.rs index 1e446026..59a9f589 100644 --- a/jive-api/src/services/family_service.rs +++ b/jive-api/src/services/family_service.rs @@ -61,18 +61,20 @@ impl FamilyService { }; // Create family + tracing::info!(target: "family_service", user_id = %user_id, name = %family_name, "Inserting family with owner_id"); let family_id = Uuid::new_v4(); let invite_code = Family::generate_invite_code(); let family = sqlx::query_as::<_, Family>( r#" - INSERT INTO families (id, name, currency, timezone, locale, invite_code, member_count, created_at, updated_at) - VALUES ($1, $2, $3, $4, $5, $6, 1, $7, $8) + INSERT INTO families (id, name, owner_id, currency, timezone, locale, invite_code, member_count, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, 1, $8, $9) RETURNING * "# ) .bind(family_id) .bind(&family_name) + .bind(user_id) .bind(request.currency.as_deref().unwrap_or("CNY")) .bind(request.timezone.as_deref().unwrap_or("Asia/Shanghai")) .bind(request.locale.as_deref().unwrap_or("zh-CN")) @@ -103,7 +105,7 @@ impl FamilyService { // Create default ledger sqlx::query( r#" - INSERT INTO ledgers (id, family_id, name, currency, owner_id, is_default, created_at, updated_at) + INSERT INTO ledgers (id, family_id, name, currency, created_by, is_default, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, true, $6, $7) "# ) diff --git a/jive-api/tests/integration/auth_register_enhanced_route_e2e_test.rs b/jive-api/tests/integration/auth_register_enhanced_route_e2e_test.rs new file mode 100644 index 00000000..d1112711 --- /dev/null +++ b/jive-api/tests/integration/auth_register_enhanced_route_e2e_test.rs @@ -0,0 +1,59 @@ +#[cfg(test)] +mod tests { + use axum::{Router, routing::{post, get}}; + use http::{Request, header, StatusCode}; + use hyper::Body; + use tower::ServiceExt; // for oneshot + use serde_json::json; + use uuid::Uuid; + + use jive_money_api::handlers::{enhanced_profile::register_with_preferences, transactions::export_transactions_csv_stream}; + use crate::fixtures::create_test_pool; + + #[tokio::test] + async fn register_enhanced_route_creates_family_and_allows_export() { + let pool = create_test_pool().await; + + let app = Router::new() + .route("/api/v1/auth/register-enhanced", post(register_with_preferences)) + .route("/api/v1/transactions/export.csv", get(export_transactions_csv_stream)) + .with_state(pool.clone()); + + let email = format!("enh_{}@example.com", Uuid::new_v4()); + let body = json!({ + "email": email, + "password": "EnhE2e123!", + "name": "EnhE2E", + "country": "CN", + "currency": "CNY", + "language": "zh-CN", + "timezone": "Asia/Shanghai", + "date_format": "YYYY-MM-DD" + }); + + let req = Request::builder() + .method("POST") + .uri("/api/v1/auth/register-enhanced") + .header(header::CONTENT_TYPE, "application/json") + .body(Body::from(body.to_string())) + .unwrap(); + let resp = app.clone().oneshot(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK, "register-enhanced should return 200"); + let bytes = hyper::body::to_bytes(resp.into_body()).await.unwrap(); + let v: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); + let token = v.pointer("/data/token").and_then(|x| x.as_str()).unwrap_or(""); + assert!(!token.is_empty(), "token should be present"); + + let req2 = Request::builder() + .method("GET") + .uri("/api/v1/transactions/export.csv?include_header=true") + .header(header::AUTHORIZATION, format!("Bearer {}", token)) + .body(Body::empty()) + .unwrap(); + let resp2 = app.clone().oneshot(req2).await.unwrap(); + assert_eq!(resp2.status(), StatusCode::OK); + let body_bytes = hyper::body::to_bytes(resp2.into_body()).await.unwrap(); + assert!(body_bytes.starts_with(b"Date,Description"), "CSV header missing or incorrect"); + } +} + diff --git a/jive-api/tests/integration/auth_register_route_e2e_test.rs b/jive-api/tests/integration/auth_register_route_e2e_test.rs new file mode 100644 index 00000000..9e5c3645 --- /dev/null +++ b/jive-api/tests/integration/auth_register_route_e2e_test.rs @@ -0,0 +1,93 @@ +#[cfg(test)] +mod tests { + use axum::{Router, routing::{post, get}}; + use http::{Request, header, StatusCode}; + use hyper::Body; + use tower::ServiceExt; // for oneshot + use serde_json::json; + use uuid::Uuid; + + use jive_money_api::handlers::{auth, transactions::export_transactions_csv_stream}; + use crate::fixtures::create_test_pool; + + #[tokio::test] + async fn register_route_creates_family_and_default_ledger_and_allows_export() { + let pool = create_test_pool().await; + + // Build minimal router for the two endpoints under test + let app = Router::new() + .route("/api/v1/auth/register", post(auth::register)) + .route("/api/v1/transactions/export.csv", get(export_transactions_csv_stream)) + .with_state(pool.clone()); + + // Unique username-style email (no @) to exercise username path as well + let uname = format!("route_e2e_{}", Uuid::new_v4()); + let body = json!({ + "email": uname, + "password": "RouteE2e123!", + "name": "RouteE2E" + }); + let req = Request::builder() + .method("POST") + .uri("/api/v1/auth/register") + .header(header::CONTENT_TYPE, "application/json") + .body(Body::from(body.to_string())) + .unwrap(); + let resp = app.clone().oneshot(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK, "register should return 200"); + + let bytes = hyper::body::to_bytes(resp.into_body()).await.unwrap(); + let v: serde_json::Value = serde_json::from_slice(&bytes).unwrap(); + let token = v.get("token").and_then(|x| x.as_str()).unwrap_or(""); + assert!(!token.is_empty(), "token should be present in register response"); + + // Find created user_id from response and assert family/ledger rows + let user_id: Uuid = serde_json::from_value(v.get("user_id").cloned().unwrap()).unwrap(); + + // families.owner_id must equal user_id + let fam_row: Option<(Uuid, Uuid)> = sqlx::query_as( + "SELECT id, owner_id FROM families WHERE owner_id = $1 ORDER BY created_at DESC LIMIT 1" + ) + .bind(user_id) + .fetch_optional(&pool) + .await + .expect("query families"); + let (family_id, owner_id) = fam_row.expect("family created"); + assert_eq!(owner_id, user_id, "families.owner_id should equal user_id"); + + // default ledger exists with created_by = user_id and is_default = true + #[derive(sqlx::FromRow, Debug)] + struct LedgerRow { id: Uuid, is_default: Option, created_by: Option } + let ledgers: Vec = sqlx::query_as( + "SELECT id, is_default, created_by FROM ledgers WHERE family_id = $1" + ) + .bind(family_id) + .fetch_all(&pool) + .await + .expect("query ledgers"); + assert_eq!(ledgers.len(), 1, "exactly one default ledger expected"); + let l = &ledgers[0]; + assert_eq!(l.is_default.unwrap_or(false), true, "ledger should be default"); + assert_eq!(l.created_by.unwrap(), user_id, "ledger.created_by should equal user_id"); + + // Now call export.csv using the token; expect header-only CSV + let req2 = Request::builder() + .method("GET") + .uri("/api/v1/transactions/export.csv?include_header=true") + .header(header::AUTHORIZATION, format!("Bearer {}", token)) + .body(Body::empty()) + .unwrap(); + let resp2 = app.clone().oneshot(req2).await.unwrap(); + assert_eq!(resp2.status(), StatusCode::OK, "export.csv should be 200"); + let body_bytes = hyper::body::to_bytes(resp2.into_body()).await.unwrap(); + let head = String::from_utf8_lossy(&body_bytes); + assert!(head.starts_with("Date,Description"), "CSV header missing or incorrect"); + + // Cleanup user rows (cascade should remove memberships/related rows) + let _ = sqlx::query("DELETE FROM users WHERE id = $1") + .bind(user_id) + .execute(&pool) + .await; + } +} + diff --git a/jive-api/tests/integration/mod.rs b/jive-api/tests/integration/mod.rs index 7b4f3f3f..245d54f3 100644 --- a/jive-api/tests/integration/mod.rs +++ b/jive-api/tests/integration/mod.rs @@ -1,2 +1,3 @@ mod family_flow_test; mod transactions_export_test; +mod auth_register_route_e2e_test; From ad1c1aa579364f685ffacbb4128e735f338cf485 Mon Sep 17 00:00:00 2001 From: zensgit <77236085+zensgit@users.noreply.github.com> Date: Sat, 27 Sep 2025 10:11:30 +0800 Subject: [PATCH 2/5] flutter: fix DashboardOverview context threading for Theme.of usage --- .../dashboard/dashboard_overview.dart | 22 ++++++++-------- .../lib/widgets/permission_guard.dart | 26 ++++++++++--------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/jive-flutter/lib/ui/components/dashboard/dashboard_overview.dart b/jive-flutter/lib/ui/components/dashboard/dashboard_overview.dart index cad4f483..17a48cb9 100644 --- a/jive-flutter/lib/ui/components/dashboard/dashboard_overview.dart +++ b/jive-flutter/lib/ui/components/dashboard/dashboard_overview.dart @@ -33,7 +33,7 @@ class DashboardOverview extends StatelessWidget { const SizedBox(height: 20), // 余额趋势图表 - if (data.balanceData.isNotEmpty) _buildBalanceChart(), + if (data.balanceData.isNotEmpty) _buildBalanceChart(context), const SizedBox(height: 20), @@ -54,12 +54,12 @@ class DashboardOverview extends StatelessWidget { const SizedBox(height: 20), // 账户概览 - if (data.accounts.isNotEmpty) _buildAccountsOverview(), + if (data.accounts.isNotEmpty) _buildAccountsOverview(context), const SizedBox(height: 20), // 预算概览 - if (data.budgets.isNotEmpty) _buildBudgetOverview(), + if (data.budgets.isNotEmpty) _buildBudgetOverview(context), // 底部安全区域 const SizedBox(height: 20), @@ -69,7 +69,7 @@ class DashboardOverview extends StatelessWidget { ); } - Widget _buildBalanceChart() { + Widget _buildBalanceChart(BuildContext context) { return Card( elevation: 1, shape: RoundedRectangleBorder( @@ -93,7 +93,7 @@ class DashboardOverview extends StatelessWidget { ], ), const SizedBox(height: 20), - const SizedBox( + SizedBox( height: 200, child: BalanceChart( data: data.balanceData, @@ -143,7 +143,7 @@ class DashboardOverview extends StatelessWidget { ); } - Widget _buildAccountsOverview() { + Widget _buildAccountsOverview(BuildContext context) { return Card( elevation: 1, shape: RoundedRectangleBorder( @@ -171,7 +171,7 @@ class DashboardOverview extends StatelessWidget { ), const SizedBox(height: 16), ...data.accounts.take(3).map( - (account) => _buildAccountItem(account), + (account) => _buildAccountItem(context, account), ), ], ), @@ -179,7 +179,7 @@ class DashboardOverview extends StatelessWidget { ); } - Widget _buildAccountItem(AccountOverviewData account) { + Widget _buildAccountItem(BuildContext context, AccountOverviewData account) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( @@ -231,7 +231,7 @@ class DashboardOverview extends StatelessWidget { ); } - Widget _buildBudgetOverview() { + Widget _buildBudgetOverview(BuildContext context) { return Card( elevation: 1, shape: RoundedRectangleBorder( @@ -259,7 +259,7 @@ class DashboardOverview extends StatelessWidget { ), const SizedBox(height: 16), ...data.budgets.take(3).map( - (budget) => _buildBudgetItem(budget), + (budget) => _buildBudgetItem(context, budget), ), ], ), @@ -267,7 +267,7 @@ class DashboardOverview extends StatelessWidget { ); } - Widget _buildBudgetItem(BudgetOverviewData budget) { + Widget _buildBudgetItem(BuildContext context, BudgetOverviewData budget) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Column( diff --git a/jive-flutter/lib/widgets/permission_guard.dart b/jive-flutter/lib/widgets/permission_guard.dart index e656b4ad..bb49c3ef 100644 --- a/jive-flutter/lib/widgets/permission_guard.dart +++ b/jive-flutter/lib/widgets/permission_guard.dart @@ -133,36 +133,38 @@ class PermissionButton extends ConsumerWidget { Widget button; - if (child is ElevatedButton) { - final elevatedButton = child as ElevatedButton; + final safeChild = child; + + if (safeChild is ElevatedButton) { + final elevatedButton = safeChild as ElevatedButton; button = ElevatedButton( onPressed: hasPermission ? onPressed ?? elevatedButton.onPressed : null, style: elevatedButton.style, - child: elevatedButton.child, + child: elevatedButton.child ?? const SizedBox.shrink(), ); - } else if (child is TextButton) { - final textButton = child as TextButton; + } else if (safeChild is TextButton) { + final textButton = safeChild as TextButton; button = TextButton( onPressed: hasPermission ? onPressed ?? textButton.onPressed : null, style: textButton.style, - child: textButton.child, + child: textButton.child ?? const SizedBox.shrink(), ); - } else if (child is IconButton) { - final iconButton = child as IconButton; + } else if (safeChild is IconButton) { + final iconButton = safeChild as IconButton; button = IconButton( onPressed: hasPermission ? onPressed ?? iconButton.onPressed : null, icon: iconButton.icon, tooltip: iconButton.tooltip, ); - } else if (child is FilledButton) { - final filledButton = child as FilledButton; + } else if (safeChild is FilledButton) { + final filledButton = safeChild as FilledButton; button = FilledButton( onPressed: hasPermission ? onPressed ?? filledButton.onPressed : null, style: filledButton.style, - child: filledButton.child, + child: filledButton.child ?? const SizedBox.shrink(), ); } else { - button = child; + button = safeChild; } if (!hasPermission && showTooltipWhenDisabled) { From 11f42eb851d48a401cc9ba5702f971f9829ac32f Mon Sep 17 00:00:00 2001 From: zensgit <77236085+zensgit@users.noreply.github.com> Date: Sat, 27 Sep 2025 13:25:48 +0800 Subject: [PATCH 3/5] =?UTF-8?q?flutter:=20lint=20tidy=20=E2=80=94=20remove?= =?UTF-8?q?=20unused=20vars=20in=20PermissionGuard;=20simplify=20null-awar?= =?UTF-8?q?e=20in=20AccountAdapter;=20minor=20cast=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/core/storage/adapters/account_adapter.dart | 2 +- jive-flutter/lib/widgets/permission_guard.dart | 12 ++++-------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/jive-flutter/lib/core/storage/adapters/account_adapter.dart b/jive-flutter/lib/core/storage/adapters/account_adapter.dart index 75e5446c..d94d4fa0 100644 --- a/jive-flutter/lib/core/storage/adapters/account_adapter.dart +++ b/jive-flutter/lib/core/storage/adapters/account_adapter.dart @@ -53,7 +53,7 @@ class AccountAdapter extends TypeAdapter { ..writeByte(6) ..write(obj.description) ..writeByte(7) - ..write(obj.color == null ? null : obj.color!.toARGB32()) + ..write(obj.color?.toARGB32()) ..writeByte(8) ..write(obj.isDefault) ..writeByte(9) diff --git a/jive-flutter/lib/widgets/permission_guard.dart b/jive-flutter/lib/widgets/permission_guard.dart index bb49c3ef..bf0b9eda 100644 --- a/jive-flutter/lib/widgets/permission_guard.dart +++ b/jive-flutter/lib/widgets/permission_guard.dart @@ -74,7 +74,6 @@ class PermissionGuard extends ConsumerWidget { Widget _buildDeniedWidget(BuildContext context) { final theme = Theme.of(context); - return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( @@ -136,28 +135,28 @@ class PermissionButton extends ConsumerWidget { final safeChild = child; if (safeChild is ElevatedButton) { - final elevatedButton = safeChild as ElevatedButton; + final elevatedButton = safeChild; button = ElevatedButton( onPressed: hasPermission ? onPressed ?? elevatedButton.onPressed : null, style: elevatedButton.style, child: elevatedButton.child ?? const SizedBox.shrink(), ); } else if (safeChild is TextButton) { - final textButton = safeChild as TextButton; + final textButton = safeChild; button = TextButton( onPressed: hasPermission ? onPressed ?? textButton.onPressed : null, style: textButton.style, child: textButton.child ?? const SizedBox.shrink(), ); } else if (safeChild is IconButton) { - final iconButton = safeChild as IconButton; + final iconButton = safeChild; button = IconButton( onPressed: hasPermission ? onPressed ?? iconButton.onPressed : null, icon: iconButton.icon, tooltip: iconButton.tooltip, ); } else if (safeChild is FilledButton) { - final filledButton = safeChild as FilledButton; + final filledButton = safeChild; button = FilledButton( onPressed: hasPermission ? onPressed ?? filledButton.onPressed : null, style: filledButton.style, @@ -191,7 +190,6 @@ class RoleBadge extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = Theme.of(context); final color = _getRoleColor(role); final icon = _getRoleIcon(role); final label = _getRoleLabel(role); @@ -282,8 +280,6 @@ class PermissionHint extends StatelessWidget { @override Widget build(BuildContext context) { - final theme = Theme.of(context); - return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( From af68bb3d6fc2b9d4e348828ab0c7714a98ed6835 Mon Sep 17 00:00:00 2001 From: zensgit <77236085+zensgit@users.noreply.github.com> Date: Sat, 27 Sep 2025 13:40:19 +0800 Subject: [PATCH 4/5] =?UTF-8?q?flutter:=20lint=20cleanup=20=E2=80=94=20wit?= =?UTF-8?q?hOpacity=E2=86=92withValues;=20add=20consts=20in=20invite=20dia?= =?UTF-8?q?log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/widgets/invite_member_dialog.dart | 18 +++++++++--------- jive-flutter/tag_demo.dart | 2 +- jive-flutter/test_tag_functionality.dart | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/jive-flutter/lib/widgets/invite_member_dialog.dart b/jive-flutter/lib/widgets/invite_member_dialog.dart index 06768ef0..88c40881 100644 --- a/jive-flutter/lib/widgets/invite_member_dialog.dart +++ b/jive-flutter/lib/widgets/invite_member_dialog.dart @@ -346,12 +346,12 @@ Jive Money - 集腋记账 width: double.infinity, child: ElevatedButton.icon( onPressed: _copyInviteLink, - icon: Icon(Icons.link), - label: Text('复制邀请链接'), + icon: const Icon(Icons.link), + label: const Text('复制邀请链接'), style: ElevatedButton.styleFrom( backgroundColor: Colors.black, foregroundColor: Colors.white, - padding: EdgeInsets.symmetric(vertical: 12), + padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), @@ -360,12 +360,12 @@ Jive Money - 集腋记账 width: double.infinity, child: OutlinedButton.icon( onPressed: _copyEmailContent, - icon: Icon(Icons.email), - label: Text('复制邀请邮件内容'), + icon: const Icon(Icons.email), + label: const Text('复制邀请邮件内容'), style: OutlinedButton.styleFrom( foregroundColor: Colors.black, - side: BorderSide(color: Colors.black), - padding: EdgeInsets.symmetric(vertical: 12), + side: const BorderSide(color: Colors.black), + padding: const EdgeInsets.symmetric(vertical: 12), ), ), ), @@ -374,7 +374,7 @@ Jive Money - 集腋记账 width: double.infinity, child: TextButton( onPressed: () => Navigator.pop(context), - child: Text('关闭'), + child: const Text('关闭'), ), ), ], @@ -418,7 +418,7 @@ Jive Money - 集腋记账 child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox( + SizedBox( width: 60, child: Text( '$label:', diff --git a/jive-flutter/tag_demo.dart b/jive-flutter/tag_demo.dart index 34962719..696001de 100644 --- a/jive-flutter/tag_demo.dart +++ b/jive-flutter/tag_demo.dart @@ -91,7 +91,7 @@ class TagDemoPage extends ConsumerWidget { label: Text(group.name), backgroundColor: Color( int.parse(group.color!.replaceFirst('#', '0xff'))) - .withOpacity(0.2), + .withValues(alpha: 0.2), ), ); }, diff --git a/jive-flutter/test_tag_functionality.dart b/jive-flutter/test_tag_functionality.dart index 6e86d9ed..d3ef7b0d 100644 --- a/jive-flutter/test_tag_functionality.dart +++ b/jive-flutter/test_tag_functionality.dart @@ -67,7 +67,7 @@ class TagTestScreen extends ConsumerWidget { label: Text(group.name), backgroundColor: Color(int.parse( group.color!.replaceFirst('#', '0xff'))) - .withOpacity(0.2), + .withValues(alpha: 0.2), )) .toList(), ), From 3db2b85468e056e41dbd45e9d202d62bdd305cbd Mon Sep 17 00:00:00 2001 From: zensgit <77236085+zensgit@users.noreply.github.com> Date: Sat, 27 Sep 2025 13:49:10 +0800 Subject: [PATCH 5/5] flutter: fix invalid const usage in QR placeholder; keep only safe consts --- jive-flutter/lib/widgets/qr_code_generator.dart | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/jive-flutter/lib/widgets/qr_code_generator.dart b/jive-flutter/lib/widgets/qr_code_generator.dart index ac08af16..a592bb9d 100644 --- a/jive-flutter/lib/widgets/qr_code_generator.dart +++ b/jive-flutter/lib/widgets/qr_code_generator.dart @@ -195,11 +195,11 @@ class _QrCodeGeneratorState extends State // 二维码 Center( child: _isGenerating - ? const SizedBox( + ? SizedBox( width: widget.size, height: widget.size, - child: Center( - child: CircularProgressIndicator(), + child: const Center( + child: const CircularProgressIndicator(), ), ) : ScaleTransition( @@ -229,11 +229,7 @@ class _QrCodeGeneratorState extends State embeddedImage: widget.logo != null ? AssetImage(widget.logo!) : null, - embeddedImageStyle: const QrEmbeddedImageStyle( - size: Size(60, 60), - ), padding: const EdgeInsets.all(0), - gapless: true, ), ), ),