from kivy.config import Config
Config.set('graphics', 'width', '360')
Config.set('graphics', 'height', '640')
Config.set('graphics', 'resizable', '0')
from kivy.app import App
from kivy.lang import Builder
from kivy.animation import Animation
from kivy.core.window import Window
from kivy.metrics import dp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.label import Label
from kivy.uix.modalview import ModalView
from kivy.properties import StringProperty, NumericProperty
from kivy.utils import platform
from kivy.clock import Clock
Builder.load_string('''
:
canvas:
Color:
rgba: 0, 0, 0, 0.3 if self.active else 0
Rectangle:
pos: self.pos
size: self.size
:
orientation: 'horizontal'
size_hint_y: None
height: dp(50)
spacing: dp(5)
padding: dp(5)
canvas.before:
Color:
rgba: 0.4, 0.4, 0.6, 1
Rectangle:
pos: self.pos
size: self.size
Label:
id: title_label
text: root.item_title
size_hint_x: 0.8
halign: 'left'
valign: 'middle'
text_size: self.width, None
color: 1, 1, 1, 1
font_size: dp(16)
bold: True
Button:
id: info_button
text: 'i'
size_hint_x: 0.2
size_hint_y: 1
background_normal: ''
background_color: 0.8, 0.8, 0.2, 1
font_size: dp(14)
bold: True
on_press: root.show_info()
:
size_hint: (0.8, 0.8)
auto_dismiss: True
BoxLayout:
orientation: 'vertical'
padding: dp(20)
spacing: dp(10)
canvas.before:
Color:
rgba: 0.3, 0.3, 0.5, 1
Rectangle:
pos: self.pos
size: self.size
Label:
text: root.title
font_size: dp(20)
bold: True
color: 1, 1, 1, 1
size_hint_y: 0.3
Label:
text: root.description
font_size: dp(16)
color: 1, 1, 1, 1
text_size: self.width, None
valign: 'top'
halign: 'left'
size_hint_y: 0.7
Button:
text: 'Close'
size_hint_y: 0.2
background_normal: ''
background_color: 0.8, 0.3, 0.3, 1
on_press: root.dismiss()
:
orientation: 'vertical'
spacing: dp(5)
padding: [0, 0, 0, root.keyboard_padding]
BoxLayout:
id: notification_panel
size_hint_y: None
height: 0
opacity: 0
canvas.before:
Color:
rgba: 0.2, 0.8, 0.4, 0.9
Rectangle:
pos: self.pos
size: self.size
Label:
id: notification_label
text: ''
color: 1, 1, 1, 1
font_size: dp(18)
bold: True
halign: 'center'
padding: [dp(10), dp(5)]
ScrollView:
id: content_scroll
size_hint_y: 1
do_scroll_y: True
BoxLayout:
id: items_container
orientation: 'vertical'
size_hint_y: None
height: self.minimum_height
spacing: dp(5)
padding: [dp(10), dp(0)]
BoxLayout:
id: search_controls
size_hint_y: None
height: dp(60)
padding: dp(10)
spacing: dp(5)
TextInput:
id: search_field
hint_text: 'Enter search query...'
multiline: False
size_hint_x: 0.8
padding: [dp(10), (self.height - self.line_height)/2]
font_size: dp(16)
on_focus: root.on_search_focus_changed(*args)
on_text_validate: root.perform_search()
Button:
id: action_btn
text: 'Add'
size_hint_x: 0.2
font_size: dp(13)
bold: True
background_normal: ''
background_color: (0.4, 0.6, 0.4, 1)
on_press: root.toggle_notification_panel()
ClickableOverlay:
id: keyboard_dismiss_area
size_hint: (1, 1)
active: False
on_press:
root.hide_virtual_keyboard()
self.active = False
''')
class ClickableOverlay(ButtonBehavior, Widget):
active = False
class InfoDialog(ModalView):
title = StringProperty('')
description = StringProperty('')
class ContentItem(ButtonBehavior, BoxLayout):
item_title = StringProperty('')
item_description = StringProperty('')
def __init__(self, item_title="", item_description="", **kwargs):
super().__init__(**kwargs)
self.item_title = item_title
self.item_description = item_description
def on_press(self):
app = App.get_running_app()
app.root.ids.notification_label.text = f"Selected: {self.item_title}"
if app.root.ids.notification_panel.height == 0:
app.root.show_notification_panel()
def show_info(self):
dialog = InfoDialog(title=self.item_title,
description=self.item_description)
dialog.open()
class SearchApplicationUI(BoxLayout):
keyboard_height = NumericProperty(0)
keyboard_padding = NumericProperty(0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._keyboard_listener = None
self._keyboard_measured = False
Window.bind(on_keyboard=self._on_keyboard)
# Для Android настраиваем слушатель клавиатуры
if platform == 'android':
Clock.schedule_once(self._setup_keyboard_listener, 0.5)
self.initialize_content()
Window.clearcolor = (0.4, 0.4, 0.4, 1)
def _setup_keyboard_listener(self, dt):
"""Установка слушателя клавиатуры для Android"""
try:
from android.runnable import run_on_ui_thread
from jnius import autoclass
@run_on_ui_thread
def attach_listener():
View = autoclass('android.view.View')
root_view = Window._get_activity().getWindow().getDecorView()
root_view.getViewTreeObserver().addOnGlobalLayoutListener(
KeyboardListener(self))
attach_listener()
except Exception as e:
print(f"Error setting up keyboard listener: {e}")
def _on_keyboard(self, window, key, *args):
"""Обработка аппаратной клавиатуры"""
if key == 27: # ESC key
self.hide_virtual_keyboard()
return True
return False
def on_search_focus_changed(self, instance, has_focus):
"""Измеряем высоту клавиатуры при фокусе"""
if has_focus:
if platform == 'android':
Clock.schedule_once(self._measure_keyboard_height, 0.3)
self.ids.keyboard_dismiss_area.active = True
else:
self.ids.keyboard_dismiss_area.active = False
def _measure_keyboard_height(self, dt):
"""Точное измерение высоты клавиатуры"""
try:
input_field = self.ids.search_field
pos = input_field.to_window(*input_field.pos)
# Вычисляем видимую высоту над клавиатурой
visible_height = pos[1] + input_field.height
self.keyboard_height = max(0, Window.height - visible_height)
# Обновляем отступ и показываем результат
self.keyboard_padding = self.keyboard_height
self.ids.notification_label.text = f"Keyboard: {self.keyboard_height:.1f}px"
self.show_notification_panel()
# Прокручиваем к полю ввода
Clock.schedule_once(lambda dt: self.ids.content_scroll.scroll_to(input_field), 0.1)
except Exception as e:
print(f"Keyboard measurement error: {e}")
def hide_virtual_keyboard(self):
"""Скрываем виртуальную клавиатуру"""
self.ids.search_field.focus = False
self.keyboard_padding = 0
def initialize_content(self):
"""Заполняем контент"""
for i in range(1, 21):
item = ContentItem(
item_title=f'Item {i}',
item_description=f'Detailed description for item {i}.'
)
self.ids.items_container.add_widget(item)
def toggle_notification_panel(self):
"""Переключаем панель уведомлений"""
if self.ids.notification_panel.height == 0:
query = self.ids.search_field.text.strip()
if query:
self.ids.notification_label.text = f"Added: {query}"
self.show_notification_panel()
else:
self.hide_notification_panel()
def show_notification_panel(self):
"""Показываем панель уведомлений"""
Animation(height=dp(50), opacity=1, duration=0.3).start(self.ids.notification_panel)
self.ids.action_btn.text = "Remove"
self.ids.action_btn.background_color = (0.8, 0.3, 0.3, 1)
def hide_notification_panel(self):
"""Скрываем панель уведомлений"""
Animation(height=0, opacity=0, duration=0.3).start(self.ids.notification_panel)
self.ids.action_btn.text = "Add"
self.ids.action_btn.background_color = (0.4, 0.6, 0.4, 1)
def perform_search(self):
"""Выполняем поиск"""
query = self.ids.search_field.text.strip()
if query:
self.ids.notification_label.text = f"Found: {query}"
if self.ids.notification_panel.height == 0:
self.show_notification_panel()
# Класс для слушателя клавиатуры Android
if platform == 'android':
from jnius import PythonJavaClass, java_method
class KeyboardListener(PythonJavaClass):
__javainterfaces__ = ['android/view/ViewTreeObserver$OnGlobalLayoutListener']
__javacontext__ = 'app'
def __init__(self, root_widget):
super().__init__()
self.root_widget = root_widget
@java_method('()V')
def onGlobalLayout(self):
Clock.schedule_once(lambda dt: self.root_widget._measure_keyboard_height(dt), 0)
class SearchApplication(App):
def build(self):
return SearchApplicationUI()
if __name__ == '__main__':
SearchApplication().run()