diff --git a/frontend/appflowy_flutter/integration_test/desktop/board/board_row_test.dart b/frontend/appflowy_flutter/integration_test/desktop/board/board_row_test.dart index c58c810596..068de0e279 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/board/board_row_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/board/board_row_test.dart @@ -51,6 +51,37 @@ void main() { expect(find.textContaining(name, findRichText: true), findsNWidgets(2)); }); + testWidgets('duplicate item in ToDo card then delete', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board); + const name = 'Card 1'; + final card1 = find.text(name); + await tester.hoverOnWidget( + card1, + onHover: () async { + final moreOption = find.byType(MoreCardOptionsAccessory); + await tester.tapButton(moreOption); + }, + ); + await tester.tapButtonWithName(LocaleKeys.button_duplicate.tr()); + expect(find.textContaining(name, findRichText: true), findsNWidgets(2)); + + // get the last widget that contains the name + final duplicatedCard = find.textContaining(name, findRichText: true).last; + await tester.hoverOnWidget( + duplicatedCard, + onHover: () async { + final moreOption = find.byType(MoreCardOptionsAccessory); + await tester.tapButton(moreOption); + }, + ); + await tester.tapButtonWithName(LocaleKeys.button_delete.tr()); + await tester.tapButtonWithName(LocaleKeys.button_delete.tr()); + expect(find.textContaining(name, findRichText: true), findsNWidgets(1)); + }); + testWidgets('add new group', (tester) async { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart b/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart index f7db1e6777..503eae47e2 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart +++ b/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart @@ -9,6 +9,7 @@ import 'ffi.dart'; class Log { static final shared = Log(); + // ignore: unused_field late Logger _logger; Log() { @@ -27,7 +28,9 @@ class Log { // Generic internal logging function to reduce code duplication static void _log(Level level, int rustLevel, dynamic msg, [dynamic error, StackTrace? stackTrace]) { - if (kDebugMode) { + final enableFlutterLog = false; + // ignore: dead_code + if (enableFlutterLog) { switch (level) { case Level.info: shared._logger.i(msg, stackTrace: stackTrace); @@ -83,8 +86,8 @@ Pointer toNativeUtf8(dynamic msg) { } String _formatMessageWithStackTrace(dynamic msg, StackTrace? stackTrace) { - if (stackTrace != null) { - return "$msg\nStackTrace:\n$stackTrace"; // Append the stack trace to the message - } - return msg.toString(); - } + if (stackTrace != null) { + return "$msg\nStackTrace:\n$stackTrace"; // Append the stack trace to the message + } + return msg.toString(); +} diff --git a/frontend/rust-lib/flowy-core/Cargo.toml b/frontend/rust-lib/flowy-core/Cargo.toml index 58261ca0d7..1e05603334 100644 --- a/frontend/rust-lib/flowy-core/Cargo.toml +++ b/frontend/rust-lib/flowy-core/Cargo.toml @@ -15,7 +15,7 @@ flowy-folder-pub = { workspace = true } flowy-database2 = { workspace = true } flowy-database-pub = { workspace = true } flowy-sqlite = { workspace = true } -flowy-document = { workspace = true, features = ["verbose_log"] } +flowy-document = { workspace = true } flowy-document-pub = { workspace = true } flowy-error = { workspace = true } flowy-server = { workspace = true, features = ["enable_supabase"] } diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_observe.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_observe.rs index 2a4f3dd3ee..33102a5604 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_observe.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_observe.rs @@ -192,9 +192,13 @@ async fn handle_did_update_row_orders( let row_changes = DashMap::new(); // 1. handle insert row orders for (row_order, index) in insert_row_orders { - if let Err(err) = database_editor.init_database_row(&row_order.id).await { - error!("Failed to init row: {:?}", err); - } + let row = match database_editor.init_database_row(&row_order.id).await { + Ok(database_row) => database_row.read().await.get_row().map(Arc::new), + Err(err) => { + error!("Failed to init row: {:?}", err); + None + }, + }; for database_view in database_editor.database_views.editors().await { trace!( @@ -205,21 +209,11 @@ async fn handle_did_update_row_orders( ); // insert row order in database view cache - { - let mut view_row_orders = database_view.row_orders.write().await; - if view_row_orders.len() >= index as usize { - view_row_orders.insert(index as usize, row_order.clone()); - } else { - warn!( - "[RowOrder]: insert row at index:{} out of range:{}", - index, - view_row_orders.len() - ); - } - } + database_view + .insert_row(row.clone(), index, &row_order) + .await; let is_move_row = is_move_row(&database_view, &row_order, &delete_row_indexes).await; - if let Some((index, row_detail)) = database_view.v_get_row(&row_order.id).await { database_view .v_did_create_row( diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index b86bee7baf..fbfcf3c28c 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -43,7 +43,7 @@ use dashmap::DashMap; use flowy_error::{FlowyError, FlowyResult}; use lib_infra::util::timestamp; use tokio::sync::{broadcast, RwLock}; -use tracing::{instrument, trace}; +use tracing::{instrument, trace, warn}; pub struct DatabaseViewEditor { database_id: String, @@ -127,6 +127,22 @@ impl DatabaseViewEditor { }) } + pub async fn insert_row(&self, row: Option>, index: u32, row_order: &RowOrder) { + let mut row_orders = self.row_orders.write().await; + if row_orders.len() >= index as usize { + row_orders.insert(index as usize, row_order.clone()); + } else { + warn!( + "[RowOrder]: insert row at index:{} out of range:{}", + index, + row_orders.len() + ); + } + if let Some(row) = row { + self.row_by_row_id.insert(row.id.to_string(), row); + } + } + pub async fn set_row_orders(&self, row_orders: Vec) { *self.row_orders.write().await = row_orders; } diff --git a/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs b/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs index 5e879692f4..8895654199 100644 --- a/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/group/controller_impls/default_controller.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use collab_database::fields::{Field, TypeOptionData}; use collab_database::rows::{Cells, Row, RowId}; - use flowy_error::FlowyResult; +use tracing::trace; use crate::entities::{ GroupChangesPB, GroupPB, GroupRowsNotificationPB, InsertedGroupPB, InsertedRowPB, @@ -100,6 +100,11 @@ impl GroupController for DefaultGroupController { fn did_delete_row(&mut self, row: &Row) -> FlowyResult { let mut changeset = GroupRowsNotificationPB::new(self.group.id.clone()); if self.group.contains_row(&row.id) { + trace!( + "[RowOrder]: delete row:{} from group: {}", + row.id, + self.group.id + ); self.group.remove_row(&row.id); changeset.deleted_rows.push(row.id.clone().into_inner()); }