feat(ui): enhance input method switching on query box focus

* Updated the input method switch logic to trigger when the query box gains focus.
* Improved user experience by providing clearer instructions in the UI language files.
* Added a new API endpoint for handling query box focus events.
* Implemented caching for image rendering to reduce flickering during refreshes.
This commit is contained in:
qianlifeng 2025-05-26 09:22:22 +08:00
parent f53df14bf8
commit 8e5adf7c5c
No known key found for this signature in database
10 changed files with 77 additions and 25 deletions

View File

@ -23,7 +23,7 @@
"ui_show_position_active_screen": "Active screen", "ui_show_position_active_screen": "Active screen",
"ui_show_position_last_location": "Last location", "ui_show_position_last_location": "Last location",
"ui_switch_input_method_abc": "Switch to ABC", "ui_switch_input_method_abc": "Switch to ABC",
"ui_switch_input_method_abc_tips": "When selected, the input method will be switched to english", "ui_switch_input_method_abc_tips": "When selected, the input method will be switched to english when the query box gains focus",
"ui_lang": "Language", "ui_lang": "Language",
"ui_query_hotkeys": "Query Hotkeys", "ui_query_hotkeys": "Query Hotkeys",
"ui_query_shortcuts": "Query Shortcuts", "ui_query_shortcuts": "Query Shortcuts",

View File

@ -21,7 +21,7 @@
"ui_show_position_active_screen": "Tela ativa", "ui_show_position_active_screen": "Tela ativa",
"ui_show_position_last_location": "Última posição", "ui_show_position_last_location": "Última posição",
"ui_switch_input_method_abc": "Alternar para ABC", "ui_switch_input_method_abc": "Alternar para ABC",
"ui_switch_input_method_abc_tips": "Quando selecionado, o método de entrada será alterado para o inglês", "ui_switch_input_method_abc_tips": "Quando selecionado, o método de entrada será alterado para o inglês quando a caixa de consulta receber o foco",
"ui_lang": "Idioma", "ui_lang": "Idioma",
"ui_query_hotkeys": "Teclas de atalho para consulta", "ui_query_hotkeys": "Teclas de atalho para consulta",
"ui_query_shortcuts": "Atalhos de consulta", "ui_query_shortcuts": "Atalhos de consulta",

View File

@ -21,7 +21,7 @@
"ui_show_position_active_screen": "Активный экран", "ui_show_position_active_screen": "Активный экран",
"ui_show_position_last_location": "Последнее положение", "ui_show_position_last_location": "Последнее положение",
"ui_switch_input_method_abc": "Переключить на ABC", "ui_switch_input_method_abc": "Переключить на ABC",
"ui_switch_input_method_abc_tips": "При выборе метод ввода будет переключен на английский", "ui_switch_input_method_abc_tips": "При выборе метод ввода будет переключен на английский при получении фокуса полем запроса",
"ui_lang": "Язык", "ui_lang": "Язык",
"ui_query_hotkeys": "Горячие клавиши запроса", "ui_query_hotkeys": "Горячие клавиши запроса",
"ui_query_shortcuts": "Ярлыки запросов", "ui_query_shortcuts": "Ярлыки запросов",

View File

@ -23,7 +23,7 @@
"ui_show_position_active_screen": "活动屏幕", "ui_show_position_active_screen": "活动屏幕",
"ui_show_position_last_location": "上次位置", "ui_show_position_last_location": "上次位置",
"ui_switch_input_method_abc": "切换输入法", "ui_switch_input_method_abc": "切换输入法",
"ui_switch_input_method_abc_tips": "选中后,输入法将切换到英文", "ui_switch_input_method_abc_tips": "选中后,查询框获得焦点时输入法将切换到英文",
"ui_lang": "语言", "ui_lang": "语言",
"ui_query_hotkeys": "查询快捷", "ui_query_hotkeys": "查询快捷",
"ui_query_shortcuts": "查询缩写", "ui_query_shortcuts": "查询缩写",

View File

@ -419,9 +419,13 @@ func (m *Manager) PostUIReady(ctx context.Context) {
} }
func (m *Manager) PostOnShow(ctx context.Context) { func (m *Manager) PostOnShow(ctx context.Context) {
//no-op
}
func (m *Manager) PostOnQueryBoxFocus(ctx context.Context) {
woxSetting := setting.GetSettingManager().GetWoxSetting(ctx) woxSetting := setting.GetSettingManager().GetWoxSetting(ctx)
if woxSetting.SwitchInputMethodABC { if woxSetting.SwitchInputMethodABC {
util.GetLogger().Info(ctx, "switch input method to ABC") util.GetLogger().Info(ctx, "switch input method to ABC on query box focus")
switchErr := ime.SwitchInputMethodABC() switchErr := ime.SwitchInputMethodABC()
if switchErr != nil { if switchErr != nil {
logger.Error(ctx, fmt.Sprintf("failed to switch input method to ABC: %s", switchErr.Error())) logger.Error(ctx, fmt.Sprintf("failed to switch input method to ABC: %s", switchErr.Error()))

View File

@ -50,10 +50,11 @@ var routers = map[string]func(w http.ResponseWriter, r *http.Request){
"/setting/userdata/location/update": handleUserDataLocationUpdate, "/setting/userdata/location/update": handleUserDataLocationUpdate,
// events // events
"/on/focus/lost": handleOnFocusLost, "/on/focus/lost": handleOnFocusLost,
"/on/ready": handleOnUIReady, "/on/ready": handleOnUIReady,
"/on/show": handleOnShow, "/on/show": handleOnShow,
"/on/hide": handleOnHide, "/on/querybox/focus": handleOnQueryBoxFocus,
"/on/hide": handleOnHide,
// lang // lang
"/lang/available": handleLangAvailable, "/lang/available": handleLangAvailable,
@ -699,6 +700,12 @@ func handleOnShow(w http.ResponseWriter, r *http.Request) {
writeSuccessResponse(w, "") writeSuccessResponse(w, "")
} }
func handleOnQueryBoxFocus(w http.ResponseWriter, r *http.Request) {
ctx := util.NewTraceContext()
GetUIManager().PostOnQueryBoxFocus(ctx)
writeSuccessResponse(w, "")
}
func handleOnHide(w http.ResponseWriter, r *http.Request) { func handleOnHide(w http.ResponseWriter, r *http.Request) {
ctx := util.NewTraceContext() ctx := util.NewTraceContext()

View File

@ -99,6 +99,10 @@ class WoxApi {
await WoxHttpUtil.instance.postData("/on/show", {}); await WoxHttpUtil.instance.postData("/on/show", {});
} }
Future<void> onQueryBoxFocus() async {
await WoxHttpUtil.instance.postData("/on/querybox/focus", {});
}
Future<void> onHide(PlainQuery query) async { Future<void> onHide(PlainQuery query) async {
await WoxHttpUtil.instance.postData("/on/hide", { await WoxHttpUtil.instance.postData("/on/hide", {
"query": query.toJson(), "query": query.toJson(),

View File

@ -9,6 +9,21 @@ import 'package:wox/entity/wox_image.dart';
import 'package:wox/entity/wox_theme.dart'; import 'package:wox/entity/wox_theme.dart';
import 'package:wox/enums/wox_image_type_enum.dart'; import 'package:wox/enums/wox_image_type_enum.dart';
// Image cache to prevent flickering during refreshes
class _ImageCache {
static final Map<String, Widget> _cache = {};
static Widget? get(String key) => _cache[key];
static void put(String key, Widget widget) {
if (_cache.length > 100) {
// Limit cache size
_cache.clear();
}
_cache[key] = widget;
}
}
class WoxImageView extends StatelessWidget { class WoxImageView extends StatelessWidget {
final WoxImage woxImage; final WoxImage woxImage;
final double? width; final double? width;
@ -18,8 +33,19 @@ class WoxImageView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// Create cache key based on image data and dimensions
final cacheKey = '${woxImage.imageType}_${woxImage.imageData}_${width}_$height';
// Check cache first to prevent flickering
final cachedWidget = _ImageCache.get(cacheKey);
if (cachedWidget != null) {
return cachedWidget;
}
Widget imageWidget;
if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_URL.code) { if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_URL.code) {
return Image.network( imageWidget = Image.network(
woxImage.imageData, woxImage.imageData,
width: width, width: width,
height: height, height: height,
@ -31,26 +57,33 @@ class WoxImageView extends StatelessWidget {
} else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_ABSOLUTE_PATH.code) { } else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_ABSOLUTE_PATH.code) {
// check if file exists // check if file exists
if (!File(woxImage.imageData).existsSync()) { if (!File(woxImage.imageData).existsSync()) {
return const SizedBox(width: 24, height: 24); imageWidget = const SizedBox(width: 24, height: 24);
} else {
imageWidget = Image.file(File(woxImage.imageData), width: width, height: height);
} }
return Image.file(File(woxImage.imageData), width: width, height: height);
} else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_SVG.code) { } else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_SVG.code) {
return SvgPicture.string(woxImage.imageData, width: width, height: height); imageWidget = SvgPicture.string(woxImage.imageData, width: width, height: height);
} else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_EMOJI.code) { } else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_EMOJI.code) {
return Text(woxImage.imageData, style: TextStyle(fontSize: width)); imageWidget = Text(woxImage.imageData, style: TextStyle(fontSize: width));
} else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_LOTTIE.code) { } else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_LOTTIE.code) {
final bytes = utf8.encode(woxImage.imageData); final bytes = utf8.encode(woxImage.imageData);
return Lottie.memory(bytes, width: width, height: height); imageWidget = Lottie.memory(bytes, width: width, height: height);
} else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_THEME.code) { } else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_THEME.code) {
return WoxThemeIconView(theme: WoxTheme.fromJson(jsonDecode(woxImage.imageData)), width: width, height: height); imageWidget = WoxThemeIconView(theme: WoxTheme.fromJson(jsonDecode(woxImage.imageData)), width: width, height: height);
} else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_BASE64.code) { } else if (woxImage.imageType == WoxImageTypeEnum.WOX_IMAGE_TYPE_BASE64.code) {
if (!woxImage.imageData.contains(";base64,")) { if (!woxImage.imageData.contains(";base64,")) {
return Text("Invalid image data: ${woxImage.imageData}", style: const TextStyle(color: Colors.red)); imageWidget = Text("Invalid image data: ${woxImage.imageData}", style: const TextStyle(color: Colors.red));
} else {
final imageData = woxImage.imageData.split(";base64,")[1];
imageWidget = Image.memory(base64Decode(imageData), width: width, height: height, fit: BoxFit.contain);
} }
final imageData = woxImage.imageData.split(";base64,")[1]; } else {
return Image.memory(base64Decode(imageData), width: width, height: height, fit: BoxFit.contain); imageWidget = const SizedBox(width: 24, height: 24);
} }
return const SizedBox(width: 24, height: 24);
// Cache the widget to prevent future rebuilds
_ImageCache.put(cacheKey, imageWidget);
return imageWidget;
} }
} }

View File

@ -57,9 +57,6 @@ class WoxListView<T> extends StatelessWidget {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
itemCount: controller.items.length, itemCount: controller.items.length,
itemExtent: WoxThemeUtil.instance.getResultListViewHeightByCount(1), itemExtent: WoxThemeUtil.instance.getResultListViewHeightByCount(1),
addAutomaticKeepAlives: false,
addRepaintBoundaries: false,
addSemanticIndexes: false,
itemBuilder: (context, index) { itemBuilder: (context, index) {
var item = controller.items[index]; var item = controller.items[index];
return MouseRegion( return MouseRegion(

View File

@ -124,6 +124,14 @@ class WoxLauncherController extends GetxController {
tag: 'action', tag: 'action',
); );
// Add focus listener to query box
queryBoxFocusNode.addListener(() {
if (queryBoxFocusNode.hasFocus) {
// Call API when query box gains focus
WoxApi.instance.onQueryBoxFocus();
}
});
// Initialize doctor check info // Initialize doctor check info
doctorCheckInfo.value = DoctorCheckInfo.empty(); doctorCheckInfo.value = DoctorCheckInfo.empty();
} }
@ -726,7 +734,6 @@ class WoxLauncherController extends GetxController {
resizeHeight(); resizeHeight();
} }
// update actions list
var actions = result.value.data.actions.map((e) => WoxListItem.fromResultAction(e)).toList(); var actions = result.value.data.actions.map((e) => WoxListItem.fromResultAction(e)).toList();
var oldActionIndex = actionListViewController.activeIndex.value; var oldActionIndex = actionListViewController.activeIndex.value;
var oldActionCount = actionListViewController.items.length; var oldActionCount = actionListViewController.items.length;