mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-11-02 19:13:28 +00:00
chore: adjust ui
This commit is contained in:
parent
9291236733
commit
846172a709
@ -25,6 +25,7 @@ class SearchResultListBloc
|
|||||||
state.copyWith(
|
state.copyWith(
|
||||||
hoveredSummary: event.summary,
|
hoveredSummary: event.summary,
|
||||||
hoveredResult: null,
|
hoveredResult: null,
|
||||||
|
userHovered: event.userHovered,
|
||||||
openPageId: null,
|
openPageId: null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -38,6 +39,7 @@ class SearchResultListBloc
|
|||||||
state.copyWith(
|
state.copyWith(
|
||||||
hoveredSummary: null,
|
hoveredSummary: null,
|
||||||
hoveredResult: event.item,
|
hoveredResult: event.item,
|
||||||
|
userHovered: event.userHovered,
|
||||||
openPageId: null,
|
openPageId: null,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -55,9 +57,11 @@ class SearchResultListBloc
|
|||||||
class SearchResultListEvent with _$SearchResultListEvent {
|
class SearchResultListEvent with _$SearchResultListEvent {
|
||||||
const factory SearchResultListEvent.onHoverSummary({
|
const factory SearchResultListEvent.onHoverSummary({
|
||||||
required SearchSummaryPB summary,
|
required SearchSummaryPB summary,
|
||||||
|
required bool userHovered,
|
||||||
}) = _OnHoverSummary;
|
}) = _OnHoverSummary;
|
||||||
const factory SearchResultListEvent.onHoverResult({
|
const factory SearchResultListEvent.onHoverResult({
|
||||||
required SearchResultItem item,
|
required SearchResultItem item,
|
||||||
|
required bool userHovered,
|
||||||
}) = _OnHoverResult;
|
}) = _OnHoverResult;
|
||||||
|
|
||||||
const factory SearchResultListEvent.openPage({
|
const factory SearchResultListEvent.openPage({
|
||||||
@ -72,6 +76,7 @@ class SearchResultListState with _$SearchResultListState {
|
|||||||
@Default(null) SearchSummaryPB? hoveredSummary,
|
@Default(null) SearchSummaryPB? hoveredSummary,
|
||||||
@Default(null) SearchResultItem? hoveredResult,
|
@Default(null) SearchResultItem? hoveredResult,
|
||||||
@Default(null) String? openPageId,
|
@Default(null) String? openPageId,
|
||||||
|
@Default(false) bool userHovered,
|
||||||
}) = _SearchResultListState;
|
}) = _SearchResultListState;
|
||||||
|
|
||||||
factory SearchResultListState.initial() => const SearchResultListState();
|
factory SearchResultListState.initial() => const SearchResultListState();
|
||||||
|
|||||||
@ -17,10 +17,12 @@ class SearchResultCell extends StatefulWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
this.isTrashed = false,
|
this.isTrashed = false,
|
||||||
|
this.isHovered = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final SearchResultItem item;
|
final SearchResultItem item;
|
||||||
final bool isTrashed;
|
final bool isTrashed;
|
||||||
|
final bool isHovered;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SearchResultCell> createState() => _SearchResultCellState();
|
State<SearchResultCell> createState() => _SearchResultCellState();
|
||||||
@ -142,7 +144,10 @@ class _SearchResultCellState extends State<SearchResultCell> {
|
|||||||
onFocusChange: (hasFocus) {
|
onFocusChange: (hasFocus) {
|
||||||
setState(() {
|
setState(() {
|
||||||
context.read<SearchResultListBloc>().add(
|
context.read<SearchResultListBloc>().add(
|
||||||
SearchResultListEvent.onHoverResult(item: widget.item),
|
SearchResultListEvent.onHoverResult(
|
||||||
|
item: widget.item,
|
||||||
|
userHovered: true,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
_hasFocus = hasFocus;
|
_hasFocus = hasFocus;
|
||||||
});
|
});
|
||||||
@ -150,10 +155,13 @@ class _SearchResultCellState extends State<SearchResultCell> {
|
|||||||
child: FlowyHover(
|
child: FlowyHover(
|
||||||
onHover: (value) {
|
onHover: (value) {
|
||||||
context.read<SearchResultListBloc>().add(
|
context.read<SearchResultListBloc>().add(
|
||||||
SearchResultListEvent.onHoverResult(item: widget.item),
|
SearchResultListEvent.onHoverResult(
|
||||||
|
item: widget.item,
|
||||||
|
userHovered: true,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
isSelected: () => _hasFocus,
|
isSelected: () => _hasFocus || widget.isHovered,
|
||||||
style: HoverStyle(
|
style: HoverStyle(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
hoverColor:
|
hoverColor:
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import 'package:flutter_animate/flutter_animate.dart';
|
|||||||
import 'search_result_cell.dart';
|
import 'search_result_cell.dart';
|
||||||
import 'search_summary_cell.dart';
|
import 'search_summary_cell.dart';
|
||||||
|
|
||||||
class SearchResultList extends StatelessWidget {
|
class SearchResultList extends StatefulWidget {
|
||||||
const SearchResultList({
|
const SearchResultList({
|
||||||
required this.trash,
|
required this.trash,
|
||||||
required this.resultItems,
|
required this.resultItems,
|
||||||
@ -27,6 +27,26 @@ class SearchResultList extends StatelessWidget {
|
|||||||
final List<TrashPB> trash;
|
final List<TrashPB> trash;
|
||||||
final List<SearchResultItem> resultItems;
|
final List<SearchResultItem> resultItems;
|
||||||
final List<SearchSummaryPB> resultSummaries;
|
final List<SearchSummaryPB> resultSummaries;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SearchResultList> createState() => _SearchResultListState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SearchResultListState extends State<SearchResultList> {
|
||||||
|
late final SearchResultListBloc bloc;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
bloc = SearchResultListBloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
bloc.close();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildSectionHeader(String title) => Padding(
|
Widget _buildSectionHeader(String title) => Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8) +
|
padding: const EdgeInsets.symmetric(vertical: 8) +
|
||||||
const EdgeInsets.only(left: 8),
|
const EdgeInsets.only(left: 8),
|
||||||
@ -48,7 +68,21 @@ class SearchResultList extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (resultSummaries.isNotEmpty) {
|
|
||||||
|
if (widget.resultSummaries.isNotEmpty) {
|
||||||
|
if (!bloc.state.userHovered) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback(
|
||||||
|
(_) {
|
||||||
|
bloc.add(
|
||||||
|
SearchResultListEvent.onHoverSummary(
|
||||||
|
summary: widget.resultSummaries[0],
|
||||||
|
userHovered: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@ -56,10 +90,11 @@ class SearchResultList extends StatelessWidget {
|
|||||||
ListView.separated(
|
ListView.separated(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: resultSummaries.length,
|
itemCount: widget.resultSummaries.length,
|
||||||
separatorBuilder: (_, __) => const Divider(height: 0),
|
separatorBuilder: (_, __) => const Divider(height: 0),
|
||||||
itemBuilder: (_, index) => SearchSummaryCell(
|
itemBuilder: (_, index) => SearchSummaryCell(
|
||||||
summary: resultSummaries[index],
|
summary: widget.resultSummaries[index],
|
||||||
|
isHovered: bloc.state.hoveredSummary != null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -78,13 +113,14 @@ class SearchResultList extends StatelessWidget {
|
|||||||
ListView.separated(
|
ListView.separated(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
itemCount: resultItems.length,
|
itemCount: widget.resultItems.length,
|
||||||
separatorBuilder: (_, __) => const Divider(height: 0),
|
separatorBuilder: (_, __) => const Divider(height: 0),
|
||||||
itemBuilder: (_, index) {
|
itemBuilder: (_, index) {
|
||||||
final item = resultItems[index];
|
final item = widget.resultItems[index];
|
||||||
return SearchResultCell(
|
return SearchResultCell(
|
||||||
item: item,
|
item: item,
|
||||||
isTrashed: trash.any((t) => t.id == item.id),
|
isTrashed: widget.trash.any((t) => t.id == item.id),
|
||||||
|
isHovered: bloc.state.hoveredResult?.id == item.id,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -96,11 +132,9 @@ class SearchResultList extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
child: BlocProvider(
|
child: BlocProvider.value(
|
||||||
create: (context) => SearchResultListBloc(),
|
value: bloc,
|
||||||
child: BlocListener<SearchResultListBloc, SearchResultListState>(
|
child: BlocListener<SearchResultListBloc, SearchResultListState>(
|
||||||
listenWhen: (previous, current) =>
|
|
||||||
previous.openPageId != current.openPageId,
|
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
if (state.openPageId != null) {
|
if (state.openPageId != null) {
|
||||||
FlowyOverlay.pop(context);
|
FlowyOverlay.pop(context);
|
||||||
@ -116,18 +150,27 @@ class SearchResultList extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: 7,
|
flex: 7,
|
||||||
child: ListView(
|
child: BlocBuilder<SearchResultListBloc, SearchResultListState>(
|
||||||
shrinkWrap: true,
|
buildWhen: (previous, current) =>
|
||||||
physics: const ClampingScrollPhysics(),
|
previous.hoveredResult != current.hoveredResult ||
|
||||||
children: [
|
previous.hoveredSummary != current.hoveredSummary,
|
||||||
_buildAIOverviewSection(context),
|
builder: (context, state) {
|
||||||
const VSpace(10),
|
return ListView(
|
||||||
if (resultItems.isNotEmpty) _buildResultsSection(context),
|
shrinkWrap: true,
|
||||||
],
|
physics: const ClampingScrollPhysics(),
|
||||||
|
children: [
|
||||||
|
_buildAIOverviewSection(context),
|
||||||
|
const VSpace(10),
|
||||||
|
if (widget.resultItems.isNotEmpty)
|
||||||
|
_buildResultsSection(context),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const HSpace(10),
|
const HSpace(10),
|
||||||
if (resultItems.any((item) => item.content.isNotEmpty)) ...[
|
if (widget.resultItems
|
||||||
|
.any((item) => item.content.isNotEmpty)) ...[
|
||||||
const VerticalDivider(
|
const VerticalDivider(
|
||||||
thickness: 1.0,
|
thickness: 1.0,
|
||||||
),
|
),
|
||||||
|
|||||||
@ -14,17 +14,23 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
class SearchSummaryCell extends StatelessWidget {
|
class SearchSummaryCell extends StatelessWidget {
|
||||||
const SearchSummaryCell({
|
const SearchSummaryCell({
|
||||||
required this.summary,
|
required this.summary,
|
||||||
|
required this.isHovered,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final SearchSummaryPB summary;
|
final SearchSummaryPB summary;
|
||||||
|
final bool isHovered;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FlowyHover(
|
return FlowyHover(
|
||||||
|
isSelected: () => isHovered,
|
||||||
onHover: (value) {
|
onHover: (value) {
|
||||||
context.read<SearchResultListBloc>().add(
|
context.read<SearchResultListBloc>().add(
|
||||||
SearchResultListEvent.onHoverSummary(summary: summary),
|
SearchResultListEvent.onHoverSummary(
|
||||||
|
summary: summary,
|
||||||
|
userHovered: true,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
style: HoverStyle(
|
style: HoverStyle(
|
||||||
|
|||||||
@ -453,7 +453,6 @@ impl AIManager {
|
|||||||
if let Some(local_model) = self.local_ai.get_plugin_chat_model() {
|
if let Some(local_model) = self.local_ai.get_plugin_chat_model() {
|
||||||
let model = AIModel::local(local_model, "".to_string());
|
let model = AIModel::local(local_model, "".to_string());
|
||||||
current_active_local_ai_model = Some(model.clone());
|
current_active_local_ai_model = Some(model.clone());
|
||||||
|
|
||||||
trace!("[Model Selection] current local ai model: {}", model.name);
|
trace!("[Model Selection] current local ai model: {}", model.name);
|
||||||
models.push(model);
|
models.push(model);
|
||||||
}
|
}
|
||||||
@ -466,7 +465,7 @@ impl AIManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Global active model is the model selected by the user in the workspace settings.
|
// Global active model is the model selected by the user in the workspace settings.
|
||||||
let server_active_model = self
|
let mut server_active_model = self
|
||||||
.get_workspace_select_model()
|
.get_workspace_select_model()
|
||||||
.await
|
.await
|
||||||
.map(|m| AIModel::server(m, "".to_string()))
|
.map(|m| AIModel::server(m, "".to_string()))
|
||||||
@ -478,10 +477,14 @@ impl AIManager {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let mut user_selected_model = server_active_model.clone();
|
let mut user_selected_model = server_active_model.clone();
|
||||||
|
// when current select model is deprecated, reset the model to default
|
||||||
|
if !models.iter().any(|m| m.name == server_active_model.name) {
|
||||||
|
server_active_model = AIModel::default();
|
||||||
|
}
|
||||||
|
|
||||||
let source_key = ai_available_models_key(&source);
|
let source_key = ai_available_models_key(&source);
|
||||||
|
|
||||||
// If source is provided, try to get the user-selected model from the store. User selected
|
// We use source to identify user selected model. source can be document id or chat id.
|
||||||
// model will be used as the active model if it exists.
|
|
||||||
match self.store_preferences.get_object::<AIModel>(&source_key) {
|
match self.store_preferences.get_object::<AIModel>(&source_key) {
|
||||||
None => {
|
None => {
|
||||||
// when there is selected model and current local ai is active, then use local ai
|
// when there is selected model and current local ai is active, then use local ai
|
||||||
@ -491,6 +494,8 @@ impl AIManager {
|
|||||||
},
|
},
|
||||||
Some(mut model) => {
|
Some(mut model) => {
|
||||||
trace!("[Model Selection] user previous select model: {:?}", model);
|
trace!("[Model Selection] user previous select model: {:?}", model);
|
||||||
|
// If source is provided, try to get the user-selected model from the store. User selected
|
||||||
|
// model will be used as the active model if it exists.
|
||||||
if model.is_local {
|
if model.is_local {
|
||||||
if let Some(local_ai_model) = ¤t_active_local_ai_model {
|
if let Some(local_ai_model) = ¤t_active_local_ai_model {
|
||||||
if local_ai_model.name != model.name {
|
if local_ai_model.name != model.name {
|
||||||
@ -508,7 +513,7 @@ impl AIManager {
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|m| m.name == user_selected_model.name)
|
.find(|m| m.name == user_selected_model.name)
|
||||||
.cloned()
|
.cloned()
|
||||||
.or(Some(server_active_model));
|
.or(Some(server_active_model.clone()));
|
||||||
|
|
||||||
// Update the stored preference if a different model is used.
|
// Update the stored preference if a different model is used.
|
||||||
if let Some(ref active_model) = active_model {
|
if let Some(ref active_model) = active_model {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user