From 74f424d10014a2b29106e63ec16f4096eff6e782 Mon Sep 17 00:00:00 2001 From: fabian Date: Fri, 10 Apr 2026 15:31:22 +0100 Subject: [PATCH 01/12] camera feat v2 --- app_src/ui/screens/camera_screen.py | 88 +++++++++++++++-------- app_src/ui/screens/gallery_screen.kv | 102 ++++++++++++++++++++++++--- app_src/ui/screens/manager.py | 5 +- app_src/ui/widgets/layouts.py | 3 + app_src/utils/image_operations.py | 2 +- app_src/utils/model.py | 5 ++ 6 files changed, 162 insertions(+), 43 deletions(-) diff --git a/app_src/ui/screens/camera_screen.py b/app_src/ui/screens/camera_screen.py index f61b6b7..e63d022 100644 --- a/app_src/ui/screens/camera_screen.py +++ b/app_src/ui/screens/camera_screen.py @@ -17,6 +17,9 @@ import os from datetime import datetime +from ui.widgets.layouts import MyMDScreen, GenericStatusBarSpacer, Column +from kivymd.uix.boxlayout import MDBoxLayout + # Quality presets: (label, camera_resolution, jpeg_quality, active_color) QUALITY_PRESETS = { @@ -75,7 +78,6 @@ def on_pos(self, *args): self._apply_transform() - class RoundedButton(Button): """Button with rounded corners and background color.""" def __init__(self, **kwargs): @@ -91,12 +93,12 @@ def _update_canvas(self, *args): Color(*self.background_color) RoundedRectangle(pos=self.pos, size=self.size, radius=[10]) -from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.app import MDApp -class CameraLayout(BoxLayout): +class CameraScreen(MyMDScreen): def __init__(self, **kwargs): - super().__init__(orientation='vertical', **kwargs) + super().__init__(**kwargs) + + self.name="camera" self._current_index = 1 # 1 = front on Android self._quality = 'Medium' try: @@ -104,19 +106,22 @@ def __init__(self, **kwargs): except Exception as e: print(e) traceback.print_exc() - Clock.schedule_once(self._start_camera, 1.0) - - - # ------------------------------------------------------------------ - # UI Construction - # ------------------------------------------------------------------ + # Clock.schedule_once(self.start_camera, 1.0) def _build_ui(self): + self.root=Column() # Main background color + self.canvas.before.add(Color(*BG_COLOR)) self.canvas.before.add(RoundedRectangle(size=self.size, pos=self.pos)) self.bind(size=self._update_bg, pos=self._update_bg) + self.generic_status_bar_spacer = GenericStatusBarSpacer( + status_bar_height=self.status_bar_height, + md_bg_color=[.1, .1, .1, 1]) + self.root.add_widget(self.generic_status_bar_spacer) + + # Status label at top self.status_label = Label( text='Starting camera...', @@ -125,7 +130,7 @@ def _build_ui(self): color=(0.9, 0.9, 0.9, 1), bold=True, ) - self.add_widget(self.status_label) + self.root.add_widget(self.status_label) # Camera preview container (with rounded corners border) self.camera_container = MDBoxLayout( @@ -150,7 +155,7 @@ def _build_ui(self): keep_ratio=False ) self.camera_container.add_widget(self.camera) - self.add_widget(self.camera_container) + self.root.add_widget(self.camera_container) # Quality selection row (styled) quality_row = BoxLayout( @@ -181,7 +186,7 @@ def _build_ui(self): btn.bind(on_release=self._on_quality_btn) quality_row.add_widget(btn) self._quality_btns[name] = btn - self.add_widget(quality_row) + self.root.add_widget(quality_row) # Action buttons row btn_row = BoxLayout( @@ -204,9 +209,18 @@ def _build_ui(self): ) self.flip_btn.bind(on_release=self.flip_camera) + self.back_tn = RoundedButton( + text='Back', + font_size='14sp', + background_color=FLIP_COLOR, + on_release=self.go_to_gallery_screen, + ) + + btn_row.add_widget(self.capture_btn) btn_row.add_widget(self.flip_btn) - self.add_widget(btn_row) + btn_row.add_widget(self.back_tn) + self.root.add_widget(btn_row) # Path / info label self.path_label = Label( @@ -218,10 +232,11 @@ def _build_ui(self): valign='middle', ) self.path_label.bind(size=self.path_label.setter('text_size')) - self.add_widget(self.path_label) + self.root.add_widget(self.path_label) + self.add_widget(self.root) def _update_bg(self, *args): - self.canvas.before.clear() + self.root.canvas.before.clear() with self.canvas.before: Color(*BG_COLOR) RoundedRectangle(size=self.size, pos=self.pos, radius=[0]) @@ -246,7 +261,7 @@ def _on_quality_btn(self, btn): self._refresh_quality_btn_colors() self.status_label.text = f'Quality set to {name} - restarting...' self._set_buttons_enabled(False) - self._release_camera() + self.release_camera() Clock.schedule_once(self._reopen_camera, 0.6) def _refresh_quality_btn_colors(self): @@ -263,7 +278,7 @@ def _set_buttons_enabled(self, enabled): # Camera lifecycle # ------------------------------------------------------------------ - def _start_camera(self, dt=None): + def start_camera(self, dt=None): try: res = QUALITY_PRESETS[self._quality][0] self.camera.resolution = res @@ -275,12 +290,16 @@ def _start_camera(self, dt=None): except Exception as e: self.status_label.text = f'Error: {e}' - def _release_camera(self): + def release_camera(self): try: self.camera.play = False if self.camera._camera is not None: - self.camera._camera.stop() + try: + self.camera._camera.stop() + except Exception as e: + print("release camera error:", e) if hasattr(self.camera._camera, '_android_camera'): + print("on android release_camera") ac = self.camera._camera._android_camera if ac is not None: try: @@ -295,6 +314,7 @@ def _release_camera(self): self.camera._camera = None except Exception as e: self.status_label.text = f'Release warning: {e}' + print(f'Release warning: {e}') def _reopen_camera(self, dt): try: @@ -332,7 +352,7 @@ def _update_preview_transform(self): def flip_camera(self, *args): self.status_label.text = 'Switching camera...' self._set_buttons_enabled(False) - self._release_camera() + self.release_camera() self._current_index = 0 if self._current_index == 1 else 1 Clock.schedule_once(self._reopen_camera, 0.6) @@ -450,16 +470,24 @@ def _scan_gallery(self, filepath): except Exception: pass + def go_to_gallery_screen(self,*_): + self.release_camera() + setattr(self.manager, "current", "thumbs") -class FrontCameraApp(MDApp): - def build(self): - self.title = ' Front Camera' - return CameraLayout() +if __name__ == '__main__': + from kivymd.app import MDApp - def on_stop(self): - if hasattr(self.root, '_release_camera'): - self.root._release_camera() + + class FrontCameraApp(MDApp): + def build(self): + self.title = ' Front Camera' + self.c=CameraScreen() + Clock.schedule_once(lambda x: self.on_stop(),5) + return self.c + + def on_stop(self): + if hasattr(self.c, 'release_camera'): + self.c.release_camera() -if __name__ == '__main__': FrontCameraApp().run() \ No newline at end of file diff --git a/app_src/ui/screens/gallery_screen.kv b/app_src/ui/screens/gallery_screen.kv index 4c47ff3..369af64 100644 --- a/app_src/ui/screens/gallery_screen.kv +++ b/app_src/ui/screens/gallery_screen.kv @@ -3,6 +3,7 @@ #:import BUTTON_BG_DARK utils.constants.BUTTON_BG_DARK #:import THEME_PRIMARY_COLOR utils.constants.THEME_PRIMARY_COLOR #:import GalleryTabs utils.model.GalleryTabs +#:import Clock kivy.clock @@ -248,15 +249,94 @@ height:self.minimum_height padding:[0, 0, 0, dp(120)] + FabButtonLayout: + id: fab_btn_widget + md_bg_color: [0,0,0,0] +# md_bg_color: [.8,.8,.8,.4] - MDFabButton: - icon:"plus" - on_release: root.open_file_chooser() - pos_hint: {"right": .9,"center_y":.15} - theme_font_size: "Custom" - font_size: sp(30) - theme_bg_color: "Custom" - # md_bg_color: get_color_from_hex("#1A1B1B") if app.device_theme == "light" else "white" - # text_color: [0.8,0.8, 0.8, 1] if app.device_theme == "light" else [.3,.3,.4,1] - md_bg_color: get_color_from_hex("#262C3A") - text_color: get_color_from_hex("#98F1DD") +# size_hint:[None,None] +# size: [root.height,root.width] +# adaptive_size: 1 +# pos_hint: {"right": .9,"center_y":.15} + Column: + id: actions_container_widget +# md_bg_color: [0,.4,.3,1] + adaptive_size: 1 + opacity:0 +# pos:[0,0] + pos_hint: {"right": .93} +# pos_hint: {"right": .93,"center_y":.15} + spacing: dp(10) + MDButton: + id: add_with_phone_camera_btn + on_release: root.open_camera() +# on_release: lambda *_: Clock.schedule_once(lambda *_:setattr(root.manager,"current","camera")) + theme_bg_color: "Custom" + md_bg_color: get_color_from_hex("#262C3A") + radius: [15] +# theme_width:"Custom" +# theme_height:"Custom" +# size:[135,50] + + MDButtonText: + text: "Camera" + theme_text_color: "Custom" + text_color: get_color_from_hex("#98F1DD") +# theme_font_size: "Custom" +# padding: [10,0,0,0] +# font_size: sp(18) +# adaptive_size:1 + bold:1 + MDButtonIcon: + icon:"camera-plus" + text_color: get_color_from_hex("#98F1DD") +# theme_font_size: "Custom" +# font_size: sp(30) + MDButton: + id: add_with_phone_gallery_btn + on_release: root.open_file_chooser() + theme_bg_color: "Custom" + md_bg_color: get_color_from_hex("#262C3A") + radius: [15] + MDButtonText: + text: "Photos" + theme_text_color: "Custom" + text_color: get_color_from_hex("#98F1DD") +# theme_font_size: "Custom" +# font_size: sp(18) +# adaptive_size:1 +# padding: [10,0,0,0] + bold:1 + MDButtonIcon: + icon:"image-plus" + text_color: get_color_from_hex("#98F1DD") +# theme_font_size: "Custom" +# font_size: sp(30) + + MDFabButton: + id: fab_button + icon:"plus" + on_release: root.toggle_choice_popup(self,fab_btn_widget,add_with_phone_gallery_btn,add_with_phone_camera_btn) + theme_font_size: "Custom" + font_size: sp(30) + theme_bg_color: "Custom" + md_bg_color: get_color_from_hex("#262C3A") + text_color: get_color_from_hex("#98F1DD") +# pos_hint: {"right": .9,"center_y":.16} + pos_hint: {"right": .9,"center_y":.2} + Button: + text: "dev" + on_release: setattr(root.manager,"current","emoji") + size_hint:[None,None] + size:[50,50] +#MDFabButton: +# icon:"plus" +# on_release: root.open_file_chooser() +# pos_hint: {"right": .9,"center_y":.15} +# theme_font_size: "Custom" +# font_size: sp(30) +# theme_bg_color: "Custom" +# # md_bg_color: get_color_from_hex("#1A1B1B") if app.device_theme == "light" else "white" +# # text_color: [0.8,0.8, 0.8, 1] if app.device_theme == "light" else [.3,.3,.4,1] +# md_bg_color: +# text_color: get_color_from_hex("#98F1DD") \ No newline at end of file diff --git a/app_src/ui/screens/manager.py b/app_src/ui/screens/manager.py index a238463..fa78901 100644 --- a/app_src/ui/screens/manager.py +++ b/app_src/ui/screens/manager.py @@ -18,6 +18,7 @@ from ui.screens.welcome_screen import WelcomeScreen from ui.screens.logs_screen import LogsScreen from ui.screens.download_apk_screen import DownloadApkScreen +from ui.screens.camera_screen import CameraScreen @@ -29,12 +30,14 @@ def __init__(self, **kwargs): self.welcome_screen = WelcomeScreen() self.gallery_screen = GalleryScreen() self.full_screen = FullscreenScreen() + self.camera_screen = CameraScreen() self.settings_screen = SettingsScreen() self.log_screen = LogsScreen() self.download_apk_screen = DownloadApkScreen() self.add_widget(self.gallery_screen) self.add_widget(self.full_screen) + self.add_widget(self.camera_screen) self.add_widget(self.settings_screen) self.add_widget(self.welcome_screen) self.add_widget(self.log_screen) @@ -67,7 +70,7 @@ def __register_rotate_listener(self): def on_current(self,*args): screen_name = args[1] - is_fullscreen = screen_name in ["welcome","fullscreen","logs","update_screen"] + is_fullscreen = screen_name in ["welcome","fullscreen","logs","update_screen","camera"] if is_fullscreen and self.app.bottom_bar: self.app.bottom_bar.hide(animation=False) elif self.app.bottom_bar: diff --git a/app_src/ui/widgets/layouts.py b/app_src/ui/widgets/layouts.py index 57290a0..8749ecb 100644 --- a/app_src/ui/widgets/layouts.py +++ b/app_src/ui/widgets/layouts.py @@ -384,3 +384,6 @@ def _update_spinner_pos(self, *args): def remove(self,dt=None): if self.parent: self.parent.remove_widget(self) # Hides the spinner by removing it + app_logger.warning("Removing Loading layout from parent") + else: + app_logger.warning("Loading layout has no parent") \ No newline at end of file diff --git a/app_src/utils/image_operations.py b/app_src/utils/image_operations.py index 72c4c29..51ec819 100644 --- a/app_src/utils/image_operations.py +++ b/app_src/utils/image_operations.py @@ -48,7 +48,7 @@ def __copy_add(self, files): # print('entered',files) if not files: - Clock.schedule_once(lambda dt: self.load_saved(has_files=False))# for hiding bottom nav + Clock.schedule_once(lambda dt: self.load_saved(has_files=False))# for showing bottom nav self.hide_spinner() return new_images = [] diff --git a/app_src/utils/model.py b/app_src/utils/model.py index e89fe9f..ce31d5a 100644 --- a/app_src/utils/model.py +++ b/app_src/utils/model.py @@ -27,6 +27,10 @@ def _camera_pressed(self, *args) -> None: ... def _settings_pressed(self, *args) -> None: ... def color_tab_buttons(self, current: Literal["thumbs", "fullscreen", "settings", "welcome", "logs"]) -> None: ... +class MyBtmSheetModel: + def hide(self,animation=True) -> None: ... + def show(self,animation=True) -> None: ... + class MyScreenManagerModel: # screens gallery_screen: "GalleryScreen" @@ -61,6 +65,7 @@ class WallpaperCarouselAppModel: root_layout: object sm: MyScreenManagerModel bottom_bar: BottomButtonBarModel + btm_sheet: MyBtmSheetModel def bind(self, **kwargs: Callable[..., Any]) -> None: ... def monitor_dark_and_light_device_change(self) -> Literal["light","dark"]: ... From 4b0a9281a13f302e4f88a0df3585cdee8d1cbf6c Mon Sep 17 00:00:00 2001 From: fabian Date: Fri, 10 Apr 2026 15:31:50 +0100 Subject: [PATCH 02/12] camera feat v2 and no image choosed crash patch --- app_src/ui/screens/gallery_screen.py | 108 ++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/app_src/ui/screens/gallery_screen.py b/app_src/ui/screens/gallery_screen.py index 2b1ae83..4ed6952 100644 --- a/app_src/ui/screens/gallery_screen.py +++ b/app_src/ui/screens/gallery_screen.py @@ -2,6 +2,8 @@ from datetime import datetime from pathlib import Path +from kivymd.uix.floatlayout import MDFloatLayout + from android_notify.config import on_android_platform from kivy.clock import Clock from kivy.core.window import Window @@ -15,7 +17,7 @@ from kivymd.app import MDApp from kivymd.uix.boxlayout import MDBoxLayout -from kivymd.uix.button import MDIconButton +from kivymd.uix.button import MDIconButton, MDFabButton from kivymd.uix.gridlayout import MDGridLayout from kivymd.uix.label import MDLabel from kivymd.uix.relativelayout import MDRelativeLayout @@ -298,9 +300,8 @@ def change_preview_img_size(self,widget,number_of_cols): class GalleryScreen(MyMDScreen): current_tab = StringProperty(GalleryTabs.BOTH.value) wallpapers = ListProperty([]) - + showing_action_btns=BooleanProperty(False) def __init__(self, **kwargs): - super().__init__(**kwargs) self.app = get_app() # self.app.device_theme = "light" @@ -334,8 +335,12 @@ def __init__(self, **kwargs): # self.btm_sheet = MyBtmSheet() # self.add_widget(self.btm_sheet) # self.load_saved() + def initialize_tabs(self, no_clock=False, has_files=True): + if hasattr(self.app, "bottom_bar") and self.app.bottom_bar: + self.app.bottom_bar.show(animation=False) + if not has_files: + return - def initialize_tabs(self, no_clock=False): def run_widgets_creation( *args): self.generate_tab_widgets(tab_name=GalleryTabs.BOTH.value, wallpapers=self._filter_existing_paths(my_config.get_wallpapers())) self.generate_tab_widgets(tab_name=GalleryTabs.DAY.value, wallpapers=self._filter_existing_paths(my_config.get_day_wallpapers())) @@ -349,6 +354,7 @@ def run_widgets_creation( *args): Clock.schedule_once(run_widgets_creation) def open_file_chooser(self, *_): + self.close_choice_popup() # file_operation = FileOperation(self.update_thumbnails_method) # if platform == 'android': # from android import activity # type: ignore @@ -368,6 +374,7 @@ def open_file_chooser(self, *_): # # activity.bind(on_activity_result=test) # handling image with no permission self.app.file_operation.show_spinner() + self.app.bottom_bar.hide(animation=False) def show_chooser(dt=None): @@ -638,6 +645,99 @@ def set_widget_left_and_right_padding(self,left_padding, right_padding,rotation) self.ids.head_section.size_hint_x=1 self.ids.head_section.padding= [dp(0), dp(self.status_bar_height), dp(10), dp(0)] + def toggle_choice_popup(self,button_instance,fab_button_layout,add_with_phone_gallery_btn,add_with_phone_camera_btn): + fab_button_layout.btns = [button_instance,add_with_phone_gallery_btn,add_with_phone_camera_btn] + + if self.showing_action_btns: + self.close_choice_popup() + else: + self.open_choice_popup() + + def open_choice_popup(self): + fab_button_layout = self.ids.fab_btn_widget + button_instance = self.ids.fab_button + self.ids.fab_btn_widget.md_bg_color = [.2, .2, .2, .4] + + y=button_instance.pos[1] + height=button_instance.height + self.ids.actions_container_widget.y = height+y + 10 + self.ids.actions_container_widget.opacity = 1 + button_instance.icon = "close" + fab_button_layout.showing = True + self.showing_action_btns= True + if hasattr(self.app, "bottom_bar"): + self.app.bottom_bar.hide(animation=False) + else: + app_logger.warning("No bottom nav bar to hide, You're on Hot Reload ") + + def close_choice_popup(self): + fab_button_layout=self.ids.fab_btn_widget + button_instance=self.ids.fab_button + self.ids.fab_btn_widget.md_bg_color = [0, 0, 0, 0] + self.ids.actions_container_widget.y=-100 + self.ids.actions_container_widget.opacity = 0 + button_instance.icon = "plus" + fab_button_layout.showing = False + self.showing_action_btns= False + if hasattr(self.app, "bottom_bar"): + self.app.bottom_bar.show(animation=False) + else: + app_logger.warning("No bottom nav bar to show, You're on Hot Reload ") + + def open_camera(self,*_): + Clock.schedule_once(lambda *_:setattr(self.manager, "current", "camera")) + self.manager.current="camera" + self.manager.current_screen.start_camera() + # self.app.sm.current_screen._start_camera() + +class FabButtonLayout(MDFloatLayout): + btns=ListProperty() + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.showing=False + Clock.schedule_once(lambda dt:self.adjust_children(),1) + Clock.schedule_once(lambda dt:self.adjust_children(),4) + + def on_touch_up(self, touch): + widget_on_floating_screen = False + for each_btn in self.btns: + widget_on_floating_screen = each_btn.collide_point(*touch.pos) + if widget_on_floating_screen: + break + + if self.showing and not widget_on_floating_screen: + return True + return super(FabButtonLayout, self).on_touch_up(touch) + + def on_touch_down(self, touch): + widget_on_floating_screen = False + for each_btn in self.btns: + widget_on_floating_screen = each_btn.collide_point(*touch.pos) + if widget_on_floating_screen: + break + if self.showing and not widget_on_floating_screen: + return True + + # if self.collide_point(*touch.pos): + # print("Widget clicked!") + # # Returning True consumes the touch and stops propagation + # return True + return super(FabButtonLayout, self).on_touch_down(touch) + + def adjust_children(self): + floating_button = None + other_btns_case = None + for each_widget in self.children: + if isinstance(each_widget, MDFabButton): + floating_button = each_widget + if isinstance(each_widget,Column): + other_btns_case = each_widget + if not other_btns_case or not floating_button: + app_logger.error(f"Didn't find any children for other_btns_case:{other_btns_case}, floating_button:{floating_button}") + return + y=floating_button.pos[1] + height=floating_button.height + other_btns_case.y = height+y + 20 if __name__ == "__main__": From 6e682419d765c0d11b61a672e07c4daa7cee3295 Mon Sep 17 00:00:00 2001 From: fabian Date: Fri, 10 Apr 2026 15:35:20 +0100 Subject: [PATCH 03/12] no real need --- app_src/ui/screens/gallery_screen.kv | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app_src/ui/screens/gallery_screen.kv b/app_src/ui/screens/gallery_screen.kv index 369af64..2b8c774 100644 --- a/app_src/ui/screens/gallery_screen.kv +++ b/app_src/ui/screens/gallery_screen.kv @@ -324,11 +324,7 @@ text_color: get_color_from_hex("#98F1DD") # pos_hint: {"right": .9,"center_y":.16} pos_hint: {"right": .9,"center_y":.2} - Button: - text: "dev" - on_release: setattr(root.manager,"current","emoji") - size_hint:[None,None] - size:[50,50] + #MDFabButton: # icon:"plus" # on_release: root.open_file_chooser() From 30f66318ff250b736b52b2a9799b53392c18d29a Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 10 Apr 2026 15:55:43 +0100 Subject: [PATCH 04/12] Revise README for clarity and formatting --- README.md | 103 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 71 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index a731ec9..f9e0512 100644 --- a/README.md +++ b/README.md @@ -1,63 +1,102 @@ -Wally: A working Android wallpaper carousel with no ads. +# Wally + +**A simple, ad-free Android wallpaper carousel.** [![Build](https://github.com/Fector101/wallpaper-carousel/actions/workflows/android-release.yml/badge.svg)](https://github.com/Fector101/wallpaper-carousel/actions/workflows/android-release.yml) -[APK Download](https://github.com/Fector101/wallpaper-carousel/releases/download/v1.0.6/waller.apk) +**[Download APK](https://github.com/Fector101/wallpaper-carousel/releases/download/v1.0.6/waller.apk)** + +--- + +## Overview + +Wally automatically rotates your wallpapers using images you choose. -- The wallpaper automatically switches to a new user-selected image every 2 minutes (or at a custom interval set by the user). Users can also choose changes to happen on screen wakes. +* Change wallpapers every **2 minutes** (or any custom interval) +* Option to update wallpapers on **screen wake** +* Fully **offline** and **ad-free** +--- +## Features -## More Features +### Wallpaper Carousel -### Home screen widget -- To view Lock-screen image from home screen -- Can Tap to Enter App +* Automatically cycles through selected images +* Custom time intervals +* Optional change on screen wake -### Notification -- Large Icon to Preview Next image -- Skip Button to change upcoming Image -- Stop Button to End carousel +### Home Screen Widget -### Permissions -- Access to Only Images. -- Notifications for more user controls and preview +* View current lock-screen wallpaper from your home screen +* Tap the widget to quickly open the app -### Help -- Saving added images in app path only -- Added export button to get images if uninstalling app -- A Button to Share Images to other Apps -- App is set to Auto detect android Theme (Dark Theme Recommended 😅) +### Notification Controls -## Few Screen Samples +* Preview the next wallpaper (large icon) +* Skip to the next image +* Stop the carousel anytime -All screenshot samples are in low Res (for fast loading) -| **View Screen Design** | **Home Screen Widget** | -|-------------------------------------|--------------------------------------------------------| -|![app-view-img-screen_1_360x800](.github/docs-imgs/full-screen-design.jpg) | ![home-screen_1_360x800](.github/docs-imgs/home-screen-widget.jpg) | +### Image Import & Sharing +* Share images directly to Wally from other apps +* Easily share images from Wally to other apps --- +## Permissions -| **Gallery Screen Design** | **Preview Screen Design** | -|-------------------------------------|--------------------------------------------------------| -|![gallery-screen](.github/docs-imgs/galleryscreen.jpg) | ![preview-screen](.github/docs-imgs/fullscreen.jpg) | +* Images only — no unnecessary access +* Notifications — for controls and previews +--- + +## Help & Notes + +* Images are stored inside the app’s internal directory +* Use the Export feature before uninstalling to keep your images +* Automatically adapts to system theme (dark mode recommended) --- +## Screenshots +*All screenshots are low resolution for faster loading* -| **Request Screen Design** | **Request Screen Dark Mode** | -|-------------------------------------|--------------------------------------------------------| -|![gallery-screen](.github/docs-imgs/request-screen.jpg) | ![gallery-screen dark](.github/docs-imgs/request-screen-dark.jpg) | +### Main Screens +| View Screen | Home Screen Widget | +| ---------------------------------------------------------------- | -------------------------------------------------------- | +| ![app-view-img-screen](.github/docs-imgs/full-screen-design.jpg) | ![home-screen](.github/docs-imgs/home-screen-widget.jpg) | + +--- -**Share Image to other App** +### Gallery & Preview +| Gallery Screen | Preview Screen | +| ------------------------------------------------------ | --------------------------------------------------- | +| ![gallery-screen](.github/docs-imgs/galleryscreen.jpg) | ![preview-screen](.github/docs-imgs/fullscreen.jpg) | + +--- + +### Request Screens + +| Light Mode | Dark Mode | +| ------------------------------------------------------ | ---------------------------------------------------------- | +| ![request-light](.github/docs-imgs/request-screen.jpg) | ![request-dark](.github/docs-imgs/request-screen-dark.jpg) | + +--- + +### Share Feature + +![share-modal](.github/docs-imgs/share-modal.jpg) + +--- -![gallery-screen](.github/docs-imgs/share-modal.jpg) +## Why Wally? +* No ads +* No tracking +* Clean, focused wallpaper experience --- From 4915c43c0fc10395ab309a89d8b496328789bdfe Mon Sep 17 00:00:00 2001 From: fabian Date: Tue, 14 Apr 2026 23:09:09 +0100 Subject: [PATCH 05/12] working camera patch through java --- app_src/android/DEV.md | 11 +- app_src/android/p4a/hook.py | 5 + app_src/android/src/CameraActivity.java | 327 +++++++++++++++ app_src/main.py | 4 +- app_src/ui/screens/camera_screen.py | 511 +++++------------------- app_src/ui/screens/gallery_screen.py | 7 + app_src/ui/widgets/bottom_sheet.py | 28 +- buildozer.spec | 9 +- 8 files changed, 468 insertions(+), 434 deletions(-) create mode 100644 app_src/android/src/CameraActivity.java diff --git a/app_src/android/DEV.md b/app_src/android/DEV.md index 5034f5a..59c7487 100644 --- a/app_src/android/DEV.md +++ b/app_src/android/DEV.md @@ -205,4 +205,13 @@ jobs: name: Release v${{ steps.version.outputs.VERSION }} files: bin/waller.apk -``` \ No newline at end of file +``` +### To see Only App Logs +```shell +adb logcat | grep -E "python" +``` + + +## P4a.hook +Peek at AndroidManifest.xml File before editing with [hook.py](p4a/hook.py) +[AndroidManifest.xml Location](../../.buildozer/android/platform/build-arm64-v8a_armeabi-v7a/dists/waller/src/main/AndroidManifest.xml) diff --git a/app_src/android/p4a/hook.py b/app_src/android/p4a/hook.py index 34c4ced..56b7495 100644 --- a/app_src/android/p4a/hook.py +++ b/app_src/android/p4a/hook.py @@ -114,6 +114,11 @@ def after_apk_build(toolchain: ToolchainCL): """ manifest_file_content = manifest_file_content.replace("", f"{image_share_from_other_apps_to_app_activity}") + manifest_file_content = insert_to_end_of_xml(f""" + """,manifest_file_content) android_manifest_file_path.write_text(manifest_file_content, encoding="utf-8") print("Successfully: Updated Manifest!\n",manifest_file_content) diff --git a/app_src/android/src/CameraActivity.java b/app_src/android/src/CameraActivity.java new file mode 100644 index 0000000..5ef2a15 --- /dev/null +++ b/app_src/android/src/CameraActivity.java @@ -0,0 +1,327 @@ +package org.wally.waller; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.RippleDrawable; +import android.content.res.ColorStateList; +import android.os.Bundle; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.activity.ComponentActivity; +import androidx.camera.core.Camera; +import androidx.camera.core.CameraControl; +import androidx.camera.core.CameraInfo; +import androidx.camera.core.CameraSelector; +import androidx.camera.core.ImageCapture; +import androidx.camera.core.ImageCaptureException; +import androidx.camera.core.Preview; +import androidx.camera.lifecycle.ProcessCameraProvider; +import androidx.camera.view.PreviewView; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; + +import com.google.common.util.concurrent.ListenableFuture; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.concurrent.Executor; + +public class CameraActivity extends ComponentActivity { + + private static final String TAG = "CAMERA_DBG"; + + public static final String EXTRA_PHOTO_PATH = "photo_path"; + private static final int REQ_CAMERA = 10; + + private PreviewView previewView; + private ImageCapture imageCapture; + private ProcessCameraProvider cameraProvider; + private Executor mainExecutor; + + private Camera camera; + private CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA; + + private boolean torchOn = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.d(TAG, "onCreate"); + + mainExecutor = ContextCompat.getMainExecutor(this); + + // ===== ROOT ===== + FrameLayout root = new FrameLayout(this); + + // ===== PREVIEW ===== + previewView = new PreviewView(this); + previewView.setLayoutParams(new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT + )); + previewView.setScaleType(PreviewView.ScaleType.FILL_CENTER); + root.addView(previewView); + + // ===== BOTTOM BAR ===== + LinearLayout bar = new LinearLayout(this); + bar.setOrientation(LinearLayout.HORIZONTAL); + bar.setGravity(Gravity.CENTER); + bar.setPadding(dp(18), dp(12), dp(18), dp(24)); + + FrameLayout.LayoutParams barParams = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + barParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL; + bar.setLayoutParams(barParams); + + GradientDrawable barBg = new GradientDrawable(); + barBg.setColor(Color.parseColor("#66000000")); + barBg.setCornerRadius(dp(28)); + bar.setBackground(barBg); + + ImageButton btnFlip = makeRoundIconButton(android.R.drawable.ic_menu_rotate, dp(44)); + ImageButton btnShoot = makeRoundIconButton(android.R.drawable.ic_menu_camera, dp(72)); + ImageButton btnTorch = makeRoundIconButton(android.R.drawable.btn_star_big_off, dp(44)); + + LinearLayout.LayoutParams smallLp = new LinearLayout.LayoutParams(dp(48), dp(48)); + smallLp.leftMargin = dp(10); + smallLp.rightMargin = dp(10); + + LinearLayout.LayoutParams bigLp = new LinearLayout.LayoutParams(dp(76), dp(76)); + bigLp.leftMargin = dp(14); + bigLp.rightMargin = dp(14); + + bar.addView(btnFlip, smallLp); + bar.addView(btnShoot, bigLp); + bar.addView(btnTorch, smallLp); + + root.addView(bar); + + // ===== CLOSE ===== + ImageButton btnClose = makeRoundIconButton(android.R.drawable.ic_menu_close_clear_cancel, dp(40)); + FrameLayout.LayoutParams closeLp = new FrameLayout.LayoutParams(dp(44), dp(44)); + closeLp.gravity = Gravity.TOP | Gravity.START; + closeLp.leftMargin = dp(12); + closeLp.topMargin = dp(12); + root.addView(btnClose, closeLp); + + setContentView(root); + + // ===== BUTTONS ===== + btnClose.setOnClickListener(v -> { + Log.d(TAG, "Close clicked"); + setResult(RESULT_CANCELED); + finish(); + }); + + btnShoot.setOnClickListener(v -> { + Log.d(TAG, "Shoot clicked"); + takePhoto(); + }); + + btnFlip.setOnClickListener(v -> { + Log.d(TAG, "Flip clicked"); + cameraSelector = (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) + ? CameraSelector.DEFAULT_FRONT_CAMERA + : CameraSelector.DEFAULT_BACK_CAMERA; + Log.d(TAG, "Camera switched"); + bindCameraUseCases(); + }); + + btnTorch.setOnClickListener(v -> { + Log.d(TAG, "Torch clicked"); + toggleTorch(btnTorch); + }); + + // ===== PERMISSION ===== + Log.d(TAG, "Checking permission"); + + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) + == PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Permission granted"); + startCamera(); + } else { + Log.d(TAG, "Requesting permission"); + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.CAMERA}, + REQ_CAMERA); + } + } + + // ---------- Camera ---------- + private void startCamera() { + Log.d(TAG, "startCamera"); + + ListenableFuture future = + ProcessCameraProvider.getInstance(this); + + future.addListener(() -> { + try { + Log.d(TAG, "CameraProvider ready"); + cameraProvider = future.get(); + + previewView.post(() -> { + Log.d(TAG, "Binding camera"); + bindCameraUseCases(); + }); + + } catch (Exception e) { + Log.e(TAG, "CameraProvider error", e); + finish(); + } + }, mainExecutor); + } + + private void bindCameraUseCases() { + Log.d(TAG, "bindCameraUseCases"); + + if (cameraProvider == null) { + Log.w(TAG, "cameraProvider NULL"); + return; + } + + cameraProvider.unbindAll(); + + Preview preview = new Preview.Builder().build(); + preview.setSurfaceProvider(previewView.getSurfaceProvider()); + + imageCapture = new ImageCapture.Builder().build(); + + try { + camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture); + Log.d(TAG, "Camera bound OK"); + } catch (Exception e) { + Log.e(TAG, "Bind failed", e); + } + } + + private void takePhoto() { + Log.d(TAG, "takePhoto"); + + if (imageCapture == null) { + Log.w(TAG, "imageCapture NULL"); + return; + } + + File dir = new File(getCacheDir(), "captures"); + if (!dir.exists()) dir.mkdirs(); + + String ts = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US) + .format(System.currentTimeMillis()); + + File file = new File(dir, "IMG_" + ts + ".jpg"); + Log.d(TAG, "Saving: " + file.getAbsolutePath()); + + ImageCapture.OutputFileOptions options = + new ImageCapture.OutputFileOptions.Builder(file).build(); + + imageCapture.takePicture(options, mainExecutor, new ImageCapture.OnImageSavedCallback() { + @Override + public void onImageSaved(@NonNull ImageCapture.OutputFileResults output) { + Log.d(TAG, "Photo saved"); + + Intent data = new Intent(); + data.putExtra(EXTRA_PHOTO_PATH, file.getAbsolutePath()); + setResult(RESULT_OK, data); + finish(); + } + + @Override + public void onError(@NonNull ImageCaptureException exception) { + Log.e(TAG, "Photo error", exception); + } + }); + } + + // ---------- Torch ---------- + private void toggleTorch(ImageButton btnTorch) { + torchOn = !torchOn; + Log.d(TAG, "Torch: " + torchOn); + applyTorchState(btnTorch); + } + + private void applyTorchState(ImageButton btnTorch) { + if (camera == null) { + Log.w(TAG, "Camera NULL for torch"); + return; + } + + CameraInfo info = camera.getCameraInfo(); + CameraControl control = camera.getCameraControl(); + + if (!info.hasFlashUnit()) { + Log.w(TAG, "No flash"); + return; + } + + control.enableTorch(torchOn); + } + + // ---------- Helpers ---------- + private ImageButton makeRoundIconButton(int iconRes, int sizePx) { + ImageButton b = new ImageButton(this); + b.setImageResource(iconRes); + b.setBackground(makeRoundRippleBackground()); + b.setColorFilter(Color.WHITE); + b.setLayoutParams(new ViewGroup.LayoutParams(sizePx, sizePx)); + return b; + } + + private Drawable makeRoundRippleBackground() { + GradientDrawable circle = new GradientDrawable(); + circle.setShape(GradientDrawable.OVAL); + circle.setColor(Color.parseColor("#66000000")); + + return new RippleDrawable( + ColorStateList.valueOf(Color.parseColor("#55FFFFFF")), + circle, + null + ); + } + + private int dp(int dp) { + return (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp, + getResources().getDisplayMetrics() + ); + } + + // ---------- Permission ---------- + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + Log.d(TAG, "Permission result"); + + if (requestCode == REQ_CAMERA) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + Log.d(TAG, "Permission OK"); + startCamera(); + } else { + Log.d(TAG, "Permission denied"); + finish(); + } + } + } + + // ---------- Lifecycle ---------- + @Override protected void onStart() { super.onStart(); Log.d(TAG, "onStart"); } + @Override protected void onResume() { super.onResume(); Log.d(TAG, "onResume"); } + @Override protected void onPause() { super.onPause(); Log.d(TAG, "onPause"); } + @Override protected void onStop() { super.onStop(); Log.d(TAG, "onStop"); } + @Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy"); } +} \ No newline at end of file diff --git a/app_src/main.py b/app_src/main.py index 5851a4d..1a07de6 100644 --- a/app_src/main.py +++ b/app_src/main.py @@ -73,7 +73,7 @@ def build(self): self.bottom_bar.bind_change() # needs theme from monitor_dark_and_light_device_change # get_number_of_cols() - self.btm_sheet = MyBtmSheet() + self.btm_sheet = MyBtmSheet(change_number_or_cols=self.sm.gallery_screen.change_amount_of_columns) self.root_layout.add_widget(self.btm_sheet) self.file_operation = ImageOperation(load_saved=self.sm.gallery_screen.initialize_tabs) @@ -137,7 +137,7 @@ def set_intent_for_file_operation_class(activity_id, some_int, intent): # some_int is usually -1 when a file is chosen and 0 when no file is chosen self.file_operation.hide_spinner() try: - print("intent must be before chooser callback",activity_id,some_int,intent) + print(f"intent must be before chooser callback activity_id: {activity_id}, some_int: {some_int}, intent:{intent}") if intent: # Fix for permission Error when choosing from Internal Storage section Android FileExplorer self.file_operation.intent = intent diff --git a/app_src/ui/screens/camera_screen.py b/app_src/ui/screens/camera_screen.py index e63d022..242ff8d 100644 --- a/app_src/ui/screens/camera_screen.py +++ b/app_src/ui/screens/camera_screen.py @@ -2,24 +2,19 @@ Front-Facing Camera App - with quality selector + live preview rotation (canvas transform). Enhanced UI design with modern look & feel. """ -import traceback - -from android_notify.config import on_android_platform -from kivy.app import App -from kivy.uix.boxlayout import BoxLayout -from kivy.uix.button import Button -from kivy.uix.label import Label -from kivy.uix.camera import Camera -from kivy.graphics import PushMatrix, PopMatrix, Rotate, Scale, Color, RoundedRectangle -from kivy.clock import Clock -from kivy.utils import platform -from kivy.core.text import LabelBase - -import os + from datetime import datetime -from ui.widgets.layouts import MyMDScreen, GenericStatusBarSpacer, Column -from kivymd.uix.boxlayout import MDBoxLayout +from functools import partial + +from kivy.clock import Clock, mainthread +from android_notify.config import on_android_platform, get_package_name +from android_notify.internal.java_classes import cast, autoclass +from ui.widgets.layouts import MyMDScreen +from utils.logger import app_logger + +if on_android_platform(): + from android.permissions import request_permissions, Permission # type: ignore # Quality presets: (label, camera_resolution, jpeg_quality, active_color) QUALITY_PRESETS = { @@ -37,330 +32,107 @@ #LabelBase.register(name='Roboto', fn_regular='Roboto-Regular.ttf') # falls back to DroidSans -class RotatedCamera(Camera): - """Camera widget that rotates and optionally mirrors the live preview.""" - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._rotation_angle = 0 - self._mirror = False - self._transform_applied = False - - def set_preview_transform(self, angle, mirror): - self._rotation_angle = angle - self._mirror = mirror - self._apply_transform() - - def _apply_transform(self): +C_DEBUG = 1 - self.canvas.before.clear() - self.canvas.after.clear() +C_SCAN_OK = 0 +C_SCAN_KO = 1 +C_SCAN_PC = 2 - if self._rotation_angle == 0 and not self._mirror: - return - - with self.canvas.before: - PushMatrix() - Rotate(angle=self._rotation_angle, origin=self.center) - if self._mirror: - Scale(-1, 1, origin=(self.center_x, self.center_y)) - - with self.canvas.after: - PopMatrix() - - self._transform_applied = True - - def on_size(self, *args): - if self._transform_applied: - self._apply_transform() - - def on_pos(self, *args): - if self._transform_applied: - self._apply_transform() - - -class RoundedButton(Button): - """Button with rounded corners and background color.""" - def __init__(self, **kwargs): - super().__init__(**kwargs) - self.background_color = (0, 0, 0, 0) # transparent - self.background_normal = '' - self.bind(pos=self._update_canvas, size=self._update_canvas) - self._update_canvas() - - def _update_canvas(self, *args): - self.canvas.before.clear() - with self.canvas.before: - Color(*self.background_color) - RoundedRectangle(pos=self.pos, size=self.size, radius=[10]) +C_SCAN_QR = 69 +C_SCAN_CAMERA = C_SCAN_QR+1 class CameraScreen(MyMDScreen): def __init__(self, **kwargs): super().__init__(**kwargs) - + self._cb_end_scan = None self.name="camera" - self._current_index = 1 # 1 = front on Android - self._quality = 'Medium' - try: - self._build_ui() - except Exception as e: - print(e) - traceback.print_exc() - # Clock.schedule_once(self.start_camera, 1.0) - - def _build_ui(self): - self.root=Column() - # Main background color - - self.canvas.before.add(Color(*BG_COLOR)) - self.canvas.before.add(RoundedRectangle(size=self.size, pos=self.pos)) - self.bind(size=self._update_bg, pos=self._update_bg) - - self.generic_status_bar_spacer = GenericStatusBarSpacer( - status_bar_height=self.status_bar_height, - md_bg_color=[.1, .1, .1, 1]) - self.root.add_widget(self.generic_status_bar_spacer) - - - # Status label at top - self.status_label = Label( - text='Starting camera...', - size_hint=(1, 0.07), - font_size='14sp', - color=(0.9, 0.9, 0.9, 1), - bold=True, - ) - self.root.add_widget(self.status_label) - - # Camera preview container (with rounded corners border) - self.camera_container = MDBoxLayout( - size_hint=(1, 1), - padding=[0, 0], - md_bg_color=[1,0,0,2] - ) - with self.camera_container.canvas.before: - Color(0.2, 1, 0.2, 1) - self.camera_border = RoundedRectangle(size=self.camera_container.size, - pos=self.camera_container.pos, - radius=[15]) - self.camera_container.bind(size=self._update_camera_border)#, pos=self._update_camera_border) - - res = QUALITY_PRESETS[self._quality][0] - self.camera = RotatedCamera( - index=self._current_index, - resolution=res, - play=False, - size_hint=(1, 1), - allow_stretch=True, - keep_ratio=False - ) - self.camera_container.add_widget(self.camera) - self.root.add_widget(self.camera_container) - - # Quality selection row (styled) - quality_row = BoxLayout( - size_hint=(1, 0.09), - spacing=8, - padding=[12, 4, 12, 4], - ) - quality_label = Label( - text='Quality:', - size_hint_x=0.2, - font_size='14sp', - color=(0.9, 0.9, 0.9, 1), - halign='right', - valign='middle', - ) - quality_label.bind(size=quality_label.setter('text_size')) - quality_row.add_widget(quality_label) - - self._quality_btns = {} - for name in ('Low', 'Medium', 'High'): - color = QUALITY_PRESETS[name][2] if name == self._quality else INACTIVE_COLOR - btn = RoundedButton( - text=name, - font_size='13sp', - background_color=color, - size_hint_x=0.27, - ) - btn.bind(on_release=self._on_quality_btn) - quality_row.add_widget(btn) - self._quality_btns[name] = btn - self.root.add_widget(quality_row) - - # Action buttons row - btn_row = BoxLayout( - size_hint=(1, 0.12), - spacing=12, - padding=[20, 8, 20, 8], - ) - self.capture_btn = RoundedButton( - text='CAPTURE', - font_size='16sp', - bold=True, - background_color=CAPTURE_COLOR, - ) - self.capture_btn.bind(on_release=self.capture_photo) - - self.flip_btn = RoundedButton( - text='~FLIP', - font_size='14sp', - background_color=FLIP_COLOR, - ) - self.flip_btn.bind(on_release=self.flip_camera) - - self.back_tn = RoundedButton( - text='Back', - font_size='14sp', - background_color=FLIP_COLOR, - on_release=self.go_to_gallery_screen, - ) - - - btn_row.add_widget(self.capture_btn) - btn_row.add_widget(self.flip_btn) - btn_row.add_widget(self.back_tn) - self.root.add_widget(btn_row) - - # Path / info label - self.path_label = Label( - text='', - size_hint=(1, 0.07), - font_size='11sp', - color=(0.7, 0.9, 0.7, 1), - halign='center', - valign='middle', - ) - self.path_label.bind(size=self.path_label.setter('text_size')) - self.root.add_widget(self.path_label) - self.add_widget(self.root) - - def _update_bg(self, *args): - self.root.canvas.before.clear() - with self.canvas.before: - Color(*BG_COLOR) - RoundedRectangle(size=self.size, pos=self.pos, radius=[0]) - - def _update_camera_border(self, *args): - self.camera_container.canvas.before.clear() - with self.camera_container.canvas.before: - Color(0.2, 0.2, 0.2, 1) - RoundedRectangle(size=self.camera_container.size, - pos=self.camera_container.pos, - radius=[15]) - - # ------------------------------------------------------------------ - # Quality selection - # ------------------------------------------------------------------ - - def _on_quality_btn(self, btn): - name = btn.text - if name == self._quality: - return - self._quality = name - self._refresh_quality_btn_colors() - self.status_label.text = f'Quality set to {name} - restarting...' - self._set_buttons_enabled(False) - self.release_camera() - Clock.schedule_once(self._reopen_camera, 0.6) - - def _refresh_quality_btn_colors(self): - for name, btn in self._quality_btns.items(): - btn.background_color = QUALITY_PRESETS[name][2] if name == self._quality else INACTIVE_COLOR + self._PythonActivity = autoclass("org.kivy.android.PythonActivity") + self._Intent = autoclass("android.content.Intent") + self._ActivityCamera = autoclass(f"{get_package_name()}.CameraActivity") + + self._Activity = autoclass("android.app.Activity") + self.build() + def build(self): + print('called binding CameraScreen.build') + if on_android_platform(): + from android import activity # type: ignore + activity.bind(on_activity_result=self._on_activity_result) - def _set_buttons_enabled(self, enabled): - self.capture_btn.disabled = not enabled - self.flip_btn.disabled = not enabled - for btn in self._quality_btns.values(): - btn.disabled = not enabled + def do_photo(self, cb_end_scan): + """""" + self._cb_end_scan = cb_end_scan + if on_android_platform(): + request_permissions( + [Permission.CAMERA, Permission.VIBRATE], + self._call_lib_photo + ) + else: + #handle pc + Clock.schedule_once(partial(self._on_activity_result, C_SCAN_CAMERA, None, None), 0) - # ------------------------------------------------------------------ - # Camera lifecycle - # ------------------------------------------------------------------ + @mainthread + def _on_activity_result(self, request_code, result_code, intent, *args): + """Call from java/android if on android, otherwiise diretly from Clock.schedule_interval + *args is used only because schedule_interval pass dt. @https://kivy.org/doc/stable/api-kivy.clock.html + """ + print(f"[PY] activity_result → req={request_code}, result={result_code}, intent={intent}, args:{args}") + print(f"(C_SCAN_QR, C_SCAN_CAMERA): {C_SCAN_QR}, {C_SCAN_CAMERA}") + # PY] activity_result → req=70, result=0, intent=>, args:() - def start_camera(self, dt=None): - try: - res = QUALITY_PRESETS[self._quality][0] - self.camera.resolution = res - self.camera.index = self._current_index - self.camera.play = True - self._update_preview_transform() - side = 'Front' if self._current_index == 1 else 'Back' - self.status_label.text = f'{side} • {self._quality} ({res[0]}x{res[1]})' - except Exception as e: - self.status_label.text = f'Error: {e}' - def release_camera(self): - try: - self.camera.play = False - if self.camera._camera is not None: - try: - self.camera._camera.stop() - except Exception as e: - print("release camera error:", e) - if hasattr(self.camera._camera, '_android_camera'): - print("on android release_camera") - ac = self.camera._camera._android_camera - if ac is not None: - try: - ac.stopPreview() - except Exception: - pass - try: - ac.release() - except Exception: - pass - self.camera._camera._android_camera = None - self.camera._camera = None - except Exception as e: - self.status_label.text = f'Release warning: {e}' - print(f'Release warning: {e}') + if request_code not in (C_SCAN_QR, C_SCAN_CAMERA): + return - def _reopen_camera(self, dt): - try: - res = QUALITY_PRESETS[self._quality][0] - self.camera.resolution = res - self.camera.index = self._current_index - self.camera.play = True - self._update_preview_transform() - side = 'Front' if self._current_index == 1 else 'Back' - self.status_label.text = f'{side} • {self._quality} ({res[0]}x{res[1]})' - except Exception as e: - self.status_label.text = f'Error: {e}' - finally: - self._set_buttons_enabled(True) - - # ------------------------------------------------------------------ - # Preview rotation - # ------------------------------------------------------------------ - - def _update_preview_transform(self): - if self._current_index == 0: # Back camera - angle = -90 - mirror = False - else: # Front camera - angle = 90 - mirror = False if on_android_platform(): - self.camera.set_preview_transform(angle, mirror) + if result_code == self._Activity.RESULT_OK and intent: + if request_code == C_SCAN_QR: + print('bad still thinks QR Code stuff exists') + get_str_data = self._ActivityQR.EXTRA_QR_DATA + else: + get_str_data = self._ActivityCamera.EXTRA_PHOTO_PATH + value = intent.getStringExtra(get_str_data) + result = (C_SCAN_OK, value) + else: + msg = "QrWork::Timeout / Users ends" + result = (C_SCAN_KO, msg) + else: + # handle pc + result = "Not android" + if callable(self._cb_end_scan): + Clock.schedule_once(lambda dt: self._cb_end_scan(result), 0) + else: + app_logger.error(f"Error: {self._cb_end_scan} not collable. Skip") - # ------------------------------------------------------------------ - # Flip - # ------------------------------------------------------------------ - def flip_camera(self, *args): - self.status_label.text = 'Switching camera...' - self._set_buttons_enabled(False) - self.release_camera() - self._current_index = 0 if self._current_index == 1 else 1 - Clock.schedule_once(self._reopen_camera, 0.6) - # ------------------------------------------------------------------ - # Capture & Save (original logic unchanged) - # ------------------------------------------------------------------ + def _call_lib_photo(self, permissions, grants): + """""" + print(f"permissions:{permissions}") + if on_android_platform(): + if not all(grants): + try: + print(f"QrWork::Not all permits: %s" % repr(grants)) + except Exception as e: + print(f'unfamiliar syntax e: {e}') + return + try: + activity = cast("android.app.Activity", self._PythonActivity.mActivity) + intent = self._Intent(activity, self._ActivityCamera) + activity.startActivityForResult(intent, C_SCAN_CAMERA) + except Exception as starting_activity_error: + print(f"starting_activity_error; {starting_activity_error}") + + @mainthread + def callable_test(self, result): + print(f"callable_test from App._ret_scan_load_config {result}") + # callable_test from App._ret_scan_load_config (1, 'QrWork::Timeout / Users ends') + # callable_test from App._ret_scan_load_config (0, '/data/user/0/org.wally.waller/cache/captures/IMG_20260414_225638.jpg') + self.manager.current = "thumbs" def capture_photo(self, *args): + print(f"args:{args}") try: timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') quality_name = self._quality.lower() @@ -377,98 +149,10 @@ def capture_photo(self, *args): except Exception as e: self.status_label.text = f'Capture error: {e}' - def _fix_rotation_and_compress(self, png_path, timestamp, quality_name): - jpeg_quality = QUALITY_PRESETS[self._quality][1] - try: - from PIL import Image as PILImage - img = PILImage.open(png_path) - if platform == 'android': - if self._current_index == 1: - img = img.rotate(90, expand=True) - img = img.transpose(PILImage.FLIP_LEFT_RIGHT) - else: - img = img.rotate(-90, expand=True) - jpg_name = f'capture_{quality_name}_{timestamp}.jpg' - jpg_path = self._save_path(jpg_name) - img.convert('RGB').save(jpg_path, 'JPEG', quality=jpeg_quality, optimize=True) - os.remove(png_path) - return jpg_path - except ImportError: - return self._rotate_png_stdlib(png_path, timestamp, quality_name) - - def _rotate_png_stdlib(self, png_path, timestamp, quality_name): - import struct, zlib - with open(png_path, 'rb') as f: - data = f.read() - def read_chunks(data): - pos = 8 - chunks = {} - while pos < len(data): - length = struct.unpack('>I', data[pos:pos+4])[0] - name = data[pos+4:pos+8].decode('ascii') - chunk_data = data[pos+8:pos+8+length] - chunks.setdefault(name, []).append(chunk_data) - pos += 12 + length - return chunks - chunks = read_chunks(data) - ihdr = chunks['IHDR'][0] - w, h = struct.unpack('>II', ihdr[:8]) - bit_depth, color_type = ihdr[8], ihdr[9] - if bit_depth != 8 or color_type not in (2, 6): - return png_path - channels = 4 if color_type == 6 else 3 - raw = zlib.decompress(b''.join(chunks['IDAT'])) - rows = [] - stride = 1 + w * channels - for y in range(h): - row_bytes = raw[y * stride + 1: (y + 1) * stride] - rows.append([tuple(row_bytes[x*channels:(x+1)*channels]) for x in range(w)]) - if self._current_index == 1: - new_w, new_h = h, w - new_rows = [[rows[new_h - 1 - x][y] for x in range(new_h)] for y in range(new_w)] - new_rows = [row[::-1] for row in new_rows] - else: - new_w, new_h = h, w - new_rows = [[rows[x][new_w - 1 - y] for x in range(new_h)] for y in range(new_w)] - def make_png(rows, width, height, channels): - sig = b'\x89PNG\r\n\x1a\n' - def chunk(name, data): - c = name.encode() + data - return struct.pack('>I', len(data)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff) - ct = 6 if channels == 4 else 2 - ihdr_new = struct.pack('>IIBBBBB', width, height, 8, ct, 0, 0, 0) - raw_rows = b'' - for row in rows: - raw_rows += b'\x00' - for px in row: - raw_rows += bytes(px) - idat = zlib.compress(raw_rows, 6) - return sig + chunk('IHDR', ihdr_new) + chunk('IDAT', idat) + chunk('IEND', b'') - rotated_name = f'capture_{quality_name}_{timestamp}_r.png' - rotated_path = self._save_path(rotated_name) - with open(rotated_path, 'wb') as f: - f.write(make_png(new_rows, new_w, new_h, channels)) - os.remove(png_path) - return rotated_path - - def _save_path(self, filename): - if platform == 'android': - base = '/storage/emulated/0/Pictures/KivyCam' - else: - base = os.path.join(os.path.expanduser('~'), 'Pictures', 'KivyCam') - os.makedirs(base, exist_ok=True) - return os.path.join(base, filename) + def start_camera(self): + print("from start camera, start_camera") + self.do_photo(self.callable_test) - def _scan_gallery(self, filepath): - if platform != 'android': - return - try: - from jnius import autoclass - PythonActivity = autoclass('org.kivy.android.PythonActivity') - MediaScannerConnection = autoclass('android.media.MediaScannerConnection') - MediaScannerConnection.scanFile(PythonActivity.mActivity, [filepath], None, None) - except Exception: - pass def go_to_gallery_screen(self,*_): self.release_camera() @@ -479,9 +163,12 @@ def go_to_gallery_screen(self,*_): class FrontCameraApp(MDApp): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.c=CameraScreen() + def build(self): self.title = ' Front Camera' - self.c=CameraScreen() Clock.schedule_once(lambda x: self.on_stop(),5) return self.c diff --git a/app_src/ui/screens/gallery_screen.py b/app_src/ui/screens/gallery_screen.py index 4ed6952..84977eb 100644 --- a/app_src/ui/screens/gallery_screen.py +++ b/app_src/ui/screens/gallery_screen.py @@ -690,6 +690,13 @@ def open_camera(self,*_): self.manager.current_screen.start_camera() # self.app.sm.current_screen._start_camera() + def change_amount_of_columns(self,chosen_cols): + for each_tab,data in self.tab_instances.items(): + tab_container = data["widget"] + + for each in tab_container.walk(): + if isinstance(each, DateGroupLayout): + each.change_preview_img_size(None, chosen_cols) class FabButtonLayout(MDFloatLayout): btns=ListProperty() def __init__(self, **kwargs): diff --git a/app_src/ui/widgets/bottom_sheet.py b/app_src/ui/widgets/bottom_sheet.py index beeac11..24d954a 100644 --- a/app_src/ui/widgets/bottom_sheet.py +++ b/app_src/ui/widgets/bottom_sheet.py @@ -85,6 +85,7 @@ class TypeMapElementOptions(MDBoxLayout): class MyBtmSheet(MDBottomSheet): sheet_type = "standard" items = [] + change_number_or_cols = ObjectProperty(None) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -235,6 +236,7 @@ def on_touch_up(self,touch): self.hide() else: self.show() + def on_touch_move(self, touch): if self.enable_swiping: if self.status == "opened" and abs(touch.y - touch.oy) > self.swipe_distance: @@ -264,7 +266,6 @@ def __mark_number_of_cols_selected(self): clickable_item_ids.icon_widget.icon = "check" def __change_number_of_columns_in_store(self, caller,text): - chosen_cols = caller.cols_int my_config.set_cols(chosen_cols) clickable_children = self.walk()#caller.parent.children @@ -275,20 +276,17 @@ def __change_number_of_columns_in_store(self, caller,text): if each.cols_int == chosen_cols: each.ids["icon_widget"].icon = "check" - app = MDApp.get_running_app() - gallery_screen = app.sm.current_screen - if not isinstance(gallery_screen, GalleryScreen): - app_logger.error("Add way to use other screens") - return - - # gallery_screen = None - # for e in app.sm.screens: - # gallery_screen = e - # if isinstance(e,GalleryScreen): - # break - for each in gallery_screen.walk(): - if isinstance(each,DateGroupLayout): - each.change_preview_img_size(None,chosen_cols) + self.change_number_or_cols(chosen_cols=chosen_cols) + # + # app = MDApp.get_running_app() + # gallery_screen = app.sm.current_screen + # if not isinstance(gallery_screen, GalleryScreen): + # app_logger.error("Add way to use other screens") + # return + # + # for each in gallery_screen.walk(): + # if isinstance(each,DateGroupLayout): + # each.change_preview_img_size(None,chosen_cols) self.hide() def hide(self, animation=True): diff --git a/buildozer.spec b/buildozer.spec index c69f704..27aea36 100644 --- a/buildozer.spec +++ b/buildozer.spec @@ -31,12 +31,13 @@ osx.kivy_version = 1.9.1 fullscreen = 0 orientation = all -android.permissions = RECEIVE_BOOT_COMPLETED, INTERNET, VIBRATE, USE_EXACT_ALARM, SCHEDULE_EXACT_ALARM, FOREGROUND_SERVICE, FOREGROUND_SERVICE_SPECIAL_USE, POST_NOTIFICATIONS, SET_WALLPAPER, READ_MEDIA_IMAGES, (name=android.permission.READ_EXTERNAL_STORAGE;maxSdkVersion=32), (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=28), REQUEST_INSTALL_PACKAGES -#android.permissions = RECEIVE_BOOT_COMPLETED, INTERNET, VIBRATE, USE_EXACT_ALARM, SCHEDULE_EXACT_ALARM, FOREGROUND_SERVICE, FOREGROUND_SERVICE_DATA_SYNC, POST_NOTIFICATIONS, SET_WALLPAPER, READ_MEDIA_IMAGES, (name=android.permission.READ_EXTERNAL_STORAGE;maxSdkVersion=32), (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=28) +android.permissions = RECEIVE_BOOT_COMPLETED, INTERNET, VIBRATE, USE_EXACT_ALARM, SCHEDULE_EXACT_ALARM, FOREGROUND_SERVICE, FOREGROUND_SERVICE_SPECIAL_USE, POST_NOTIFICATIONS, SET_WALLPAPER, READ_MEDIA_IMAGES, (name=android.permission.READ_EXTERNAL_STORAGE;maxSdkVersion=32), (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=28), REQUEST_INSTALL_PACKAGES, FOREGROUND_SERVICE_DATA_SYNC, CAMERA +#android.permissions = RECEIVE_BOOT_COMPLETED, INTERNET, VIBRATE, USE_EXACT_ALARM, SCHEDULE_EXACT_ALARM, FOREGROUND_SERVICE, FOREGROUND_SERVICE_SPECIAL_USE, POST_NOTIFICATIONS, SET_WALLPAPER, READ_MEDIA_IMAGES, (name=android.permission.READ_EXTERNAL_STORAGE;maxSdkVersion=32), (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=28), REQUEST_INSTALL_PACKAGES + android.add_src = %(source.dir)s/android/src android.add_resources = %(source.dir)s/android/res -android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0 -#android.gradle_dependencies = com.google.android.material:material:1.6.0, androidx.core:core-ktx:1.15.0, androidx.core:core:1.6.0, androidx.work:work-runtime:2.9.0 +android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0,androidx.camera:camera-core:1.3.4,androidx.camera:camera-camera2:1.3.4,androidx.camera:camera-lifecycle:1.3.4,androidx.camera:camera-view:1.3.4,androidx.core:core-ktx:1.12.0,androidx.appcompat:appcompat:1.6.1,androidx.activity:activity:1.8.2,com.google.guava:guava:31.1-android +#android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0 android.enable_androidx = True p4a.hook = %(source.dir)s/android/p4a/hook.py From 8206def71e555a6267d2cce8875d8e67385e013e Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 15 Apr 2026 06:45:03 +0100 Subject: [PATCH 06/12] size issue fix test with git action --- buildozer.spec | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/buildozer.spec b/buildozer.spec index 27aea36..24b0251 100644 --- a/buildozer.spec +++ b/buildozer.spec @@ -36,7 +36,9 @@ android.permissions = RECEIVE_BOOT_COMPLETED, INTERNET, VIBRATE, USE_EXACT_ALARM android.add_src = %(source.dir)s/android/src android.add_resources = %(source.dir)s/android/res -android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0,androidx.camera:camera-core:1.3.4,androidx.camera:camera-camera2:1.3.4,androidx.camera:camera-lifecycle:1.3.4,androidx.camera:camera-view:1.3.4,androidx.core:core-ktx:1.12.0,androidx.appcompat:appcompat:1.6.1,androidx.activity:activity:1.8.2,com.google.guava:guava:31.1-android +android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0,androidx.camera:camera-core:1.3.4,androidx.camera:camera-camera2:1.3.4,androidx.camera:camera-lifecycle:1.3.4,androidx.camera:camera-view:1.3.4,androidx.core:core-ktx:1.12.0,androidx.appcompat:appcompat:1.6.1,androidx.activity:activity:1.8.2 + +# android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0,androidx.camera:camera-core:1.3.4,androidx.camera:camera-camera2:1.3.4,androidx.camera:camera-lifecycle:1.3.4,androidx.camera:camera-view:1.3.4,androidx.core:core-ktx:1.12.0,androidx.appcompat:appcompat:1.6.1,androidx.activity:activity:1.8.2,com.google.guava:guava:31.1-android #android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0 android.enable_androidx = True p4a.hook = %(source.dir)s/android/p4a/hook.py From 55f6297d63eaa143b212cf41ed12169e34514ad3 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 15 Apr 2026 09:05:35 +0100 Subject: [PATCH 07/12] something couldn't find materialyoucolor==2.0.10 --- .github/workflows/android-debug.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android-debug.yml b/.github/workflows/android-debug.yml index 6f72d8c..20bd86c 100644 --- a/.github/workflows/android-debug.yml +++ b/.github/workflows/android-debug.yml @@ -42,7 +42,7 @@ jobs: python3 -m venv venv source venv/bin/activate pip install --upgrade pip - pip install Cython==0.29.33 buildozer android-widgets + pip install Cython==0.29.34 buildozer android-widgets materialyoucolor==2.0.10 # Prepare Buildozer SDK folder - name: Prepare Android SDK folder @@ -81,7 +81,7 @@ jobs: id: build run: | source venv/bin/activate - pip install android-widgets git+https://github.com/kivy/buildozer legacy-cgi setuptools cython==0.29.34 + pip install git+https://github.com/kivy/buildozer legacy-cgi setuptools buildozer android debug APK=$(find ./bin -name "*-debug.apk" | head -n 1) echo "apk=$APK" >> $GITHUB_OUTPUT From 6faad716a5f6a5094ec03a2856f9230fc0387ca6 Mon Sep 17 00:00:00 2001 From: Fabian Date: Wed, 15 Apr 2026 09:26:19 +0100 Subject: [PATCH 08/12] test 2 --- .github/workflows/android-debug.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/android-debug.yml b/.github/workflows/android-debug.yml index 20bd86c..a45b007 100644 --- a/.github/workflows/android-debug.yml +++ b/.github/workflows/android-debug.yml @@ -42,7 +42,7 @@ jobs: python3 -m venv venv source venv/bin/activate pip install --upgrade pip - pip install Cython==0.29.34 buildozer android-widgets materialyoucolor==2.0.10 + pip install Cython==0.29.34 buildozer android-widgets materialyoucolor==2.0.10 git+https://github.com/kivy/buildozer legacy-cgi setuptools # Prepare Buildozer SDK folder - name: Prepare Android SDK folder @@ -81,7 +81,6 @@ jobs: id: build run: | source venv/bin/activate - pip install git+https://github.com/kivy/buildozer legacy-cgi setuptools buildozer android debug APK=$(find ./bin -name "*-debug.apk" | head -n 1) echo "apk=$APK" >> $GITHUB_OUTPUT From 8e747a542b0f71459b2f72a691f305f82e14b1ce Mon Sep 17 00:00:00 2001 From: fabian Date: Wed, 15 Apr 2026 09:59:44 +0100 Subject: [PATCH 09/12] revert to working action --- .github/workflows/android-debug.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android-debug.yml b/.github/workflows/android-debug.yml index a45b007..ad3773e 100644 --- a/.github/workflows/android-debug.yml +++ b/.github/workflows/android-debug.yml @@ -42,7 +42,7 @@ jobs: python3 -m venv venv source venv/bin/activate pip install --upgrade pip - pip install Cython==0.29.34 buildozer android-widgets materialyoucolor==2.0.10 git+https://github.com/kivy/buildozer legacy-cgi setuptools + pip install Cython==0.29.33 buildozer android-widgets # Prepare Buildozer SDK folder - name: Prepare Android SDK folder @@ -81,14 +81,15 @@ jobs: id: build run: | source venv/bin/activate + pip install android-widgets git+https://github.com/kivy/buildozer legacy-cgi setuptools cython==0.29.34 buildozer android debug APK=$(find ./bin -name "*-debug.apk" | head -n 1) echo "apk=$APK" >> $GITHUB_OUTPUT echo "Built APK: $APK" - + - name: Upload artifacts uses: actions/upload-artifact@v4 with: name: package - path: ${{ steps.build.outputs.apk }} + path: ${{ steps.build.outputs.apk }} \ No newline at end of file From a0f7605654c0f9e010e55242d010d07a6595a5f3 Mon Sep 17 00:00:00 2001 From: fabian Date: Wed, 15 Apr 2026 10:18:14 +0100 Subject: [PATCH 10/12] duilpcate dep --- buildozer.spec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildozer.spec b/buildozer.spec index 24b0251..f5859d3 100644 --- a/buildozer.spec +++ b/buildozer.spec @@ -36,7 +36,7 @@ android.permissions = RECEIVE_BOOT_COMPLETED, INTERNET, VIBRATE, USE_EXACT_ALARM android.add_src = %(source.dir)s/android/src android.add_resources = %(source.dir)s/android/res -android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0,androidx.camera:camera-core:1.3.4,androidx.camera:camera-camera2:1.3.4,androidx.camera:camera-lifecycle:1.3.4,androidx.camera:camera-view:1.3.4,androidx.core:core-ktx:1.12.0,androidx.appcompat:appcompat:1.6.1,androidx.activity:activity:1.8.2 +android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0,androidx.camera:camera-core:1.3.4,androidx.camera:camera-camera2:1.3.4,androidx.camera:camera-lifecycle:1.3.4,androidx.camera:camera-view:1.3.4,androidx.appcompat:appcompat:1.6.1,androidx.activity:activity:1.8.2 # android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0,androidx.camera:camera-core:1.3.4,androidx.camera:camera-camera2:1.3.4,androidx.camera:camera-lifecycle:1.3.4,androidx.camera:camera-view:1.3.4,androidx.core:core-ktx:1.12.0,androidx.appcompat:appcompat:1.6.1,androidx.activity:activity:1.8.2,com.google.guava:guava:31.1-android #android.gradle_dependencies = androidx.core:core-ktx:1.12.0, com.google.android.material:material:1.12.0 From 0478a9726c2720fc20e7809c99c35dcb08f513a0 Mon Sep 17 00:00:00 2001 From: fabian Date: Wed, 15 Apr 2026 12:02:58 +0100 Subject: [PATCH 11/12] adding image to app storage --- app_src/ui/screens/camera_screen.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app_src/ui/screens/camera_screen.py b/app_src/ui/screens/camera_screen.py index 242ff8d..f36fb64 100644 --- a/app_src/ui/screens/camera_screen.py +++ b/app_src/ui/screens/camera_screen.py @@ -3,6 +3,7 @@ Enhanced UI design with modern look & feel. """ +import traceback from datetime import datetime from functools import partial @@ -16,20 +17,7 @@ if on_android_platform(): from android.permissions import request_permissions, Permission # type: ignore -# Quality presets: (label, camera_resolution, jpeg_quality, active_color) -QUALITY_PRESETS = { - 'Low': ((320, 240), 50, (0.2, 0.6, 0.9, 1)), # blue - 'Medium': ((640, 480), 80, (0.3, 0.8, 0.4, 1)), # green - 'High': ((1280, 720), 95, (0.9, 0.5, 0.2, 1)), # orange -} -INACTIVE_COLOR = (0.3, 0.3, 0.3, 1) -BG_COLOR = (0.12, 0.12, 0.12, 1) # dark background -BUTTON_COLOR = (0.2, 0.2, 0.2, 1) -CAPTURE_COLOR = (0.9, 0.3, 0.2, 1) # reddish capture -FLIP_COLOR = (0.3, 0.5, 0.8, 1) # blue flip - -# Register a default font (optional, just for consistency) -#LabelBase.register(name='Roboto', fn_regular='Roboto-Regular.ttf') # falls back to DroidSans +from utils.model import get_app C_DEBUG = 1 @@ -42,9 +30,12 @@ C_SCAN_CAMERA = C_SCAN_QR+1 + class CameraScreen(MyMDScreen): def __init__(self, **kwargs): super().__init__(**kwargs) + self.app = get_app() + self._cb_end_scan = None self.name="camera" self._PythonActivity = autoclass("org.kivy.android.PythonActivity") @@ -53,6 +44,7 @@ def __init__(self, **kwargs): self._Activity = autoclass("android.app.Activity") self.build() + def build(self): print('called binding CameraScreen.build') if on_android_platform(): @@ -129,6 +121,15 @@ def callable_test(self, result): print(f"callable_test from App._ret_scan_load_config {result}") # callable_test from App._ret_scan_load_config (1, 'QrWork::Timeout / Users ends') # callable_test from App._ret_scan_load_config (0, '/data/user/0/org.wally.waller/cache/captures/IMG_20260414_225638.jpg') + print(f"got: {result}, [0]: {result[0]}") + if not result[0]: + complete_file_path = result[1] + print(f"using file: {complete_file_path}") + try: + self.app.file_operation.copy_add([complete_file_path]) + except Exception as error_adding_image: + print(f"error_adding_image:{error_adding_image}") + traceback.print_exc() self.manager.current = "thumbs" def capture_photo(self, *args): From a1cf59107578eed834f6896ae9265d58192eb9eb Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 16 Apr 2026 11:46:09 +0100 Subject: [PATCH 12/12] changing arch, avoiding weird error materialyoucolor==2.0.10 doesn't exist --- buildozer.spec | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/buildozer.spec b/buildozer.spec index f5859d3..199f56b 100644 --- a/buildozer.spec +++ b/buildozer.spec @@ -50,7 +50,8 @@ android.api = 35 p4a.branch = develop -android.archs = arm64-v8a, armeabi-v7a +android.archs = arm64-v8a +# android.archs = arm64-v8a, armeabi-v7a android.allow_backup = True