I use kivymd 2.0.1dev.
The button function in the topappbar menu works.
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
from kivymd.uix.card import MDCard
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.core.window import Window
from PIL import Image as PILImage
import numpy as np
import os
import random
import logging
import traceback
from kivy.logger import Logger
import sys
from kivymd.uix.appbar import (
MDTopAppBar,
MDTopAppBarTitle,
MDTopAppBarLeadingButtonContainer,
MDTopAppBarTrailingButtonContainer,
MDActionTopAppBarButton,
MDBottomAppBar,
MDFabBottomAppBarButton,
MDActionBottomAppBarButton
)
from kivymd.uix.menu import MDDropdownMenu
from kivy.properties import StringProperty, NumericProperty
from kivymd.uix.appbar import MDActionBottomAppBarButton
# Logger Configuration
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
# Set Logging Level
Logger.setLevel(logging.DEBUG)
# Add Crash Handler
def custom_exception_handler(exc_type, exc_value, exc_traceback):
"""Handle uncaught exceptions"""
Logger.error('Uncaught exception:', exc_info=(exc_type, exc_value, exc_traceback))
# Call original sys.excepthook
sys.__excepthook__(exc_type, exc_value, exc_traceback)
sys.excepthook = custom_exception_handler
def is_android():
"""Check if app is running on Android"""
try:
from kivy.utils import platform
return platform == "android"
except ImportError:
return False
def get_image_dir():
"""Determine correct image directory based on platform"""
try:
if is_android():
# Android-specific import
from android import mActivity
context = mActivity.getApplicationContext()
image_dir = os.path.join(context.getExternalFilesDir(None).getAbsolutePath(), "images")
Logger.debug(f'Android image directory: {image_dir}')
else:
# Desktop directory
image_dir = "./images"
Logger.debug(f'Desktop image directory: {image_dir}')
# Create directory if it doesn't exist
if not os.path.exists(image_dir):
os.makedirs(image_dir)
Logger.debug(f'Created image directory: {image_dir}')
return image_dir
except Exception as e:
Logger.error(f'Error creating image directory: {str(e)}')
return "./images" # Fallback
# Global image directory
IMAGE_DIR = get_image_dir()
class PhotoCard(MDCard):
"""Class for photo cards"""
source = StringProperty("") # Property for image path
index = NumericProperty(0) # Property for index
def __init__(self, photo_path, index, **kwargs):
super().__init__(**kwargs)
self.source = photo_path
self.index = index
Logger.debug(f'Created PhotoCard with image: {photo_path} and index: {index}')
class MainScreen(MDScreen):
"""Main screen of the app"""
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Define menu items
self.menu_items = [
{
"text": "Toggle Theme",
"on_release": self.toggle_theme,
"leading_icon": "theme-light-dark"
}
]
# Create dropdown menu
self.dropdown_menu = MDDropdownMenu(
items=self.menu_items,
width_mult=2,
)
# ScrollView for images (95%)
self.scroll_view = ScrollView(
size_hint=(1, 0.95),
do_scroll_x=False,
do_scroll_y=True
)
# BoxLayout for images
self.image_layout = BoxLayout(
orientation='vertical',
size_hint_y=None,
spacing='10dp',
padding=('5dp', '5dp', '5dp', '5dp')
)
self.image_layout.bind(minimum_height=self.image_layout.setter('height'))
self.scroll_view.add_widget(self.image_layout)
self.add_widget(self.scroll_view)
self.image_count = 0
# Debug events for buttons
self.bind(on_kv_post=self.setup_buttons)
def setup_buttons(self, *args):
"""Set up button events after KV loading"""
Logger.debug('Setting up button events')
try:
camera_btn = self.ids.camera_btn
exit_btn = self.ids.exit_btn
camera_btn.bind(
on_press=lambda x: Logger.debug('Camera button pressed'),
on_release=self.generate_random_image
)
exit_btn.bind(
on_press=lambda x: Logger.debug('Exit button pressed'),
on_release=self.stop_app
)
Logger.debug('Button events setup complete')
except Exception as e:
Logger.error(f'Error setting up buttons: {str(e)}')
def change_actions_items(self):
self.root.ids.bottom_appbar.action_items = [
MDActionBottomAppBarButton(icon="magnify"),
MDActionBottomAppBarButton(icon="trash-can-outline"),
MDActionBottomAppBarButton(icon="download-box-outline"),
]
def generate_random_image(self, instance):
"""Generate a new image"""
print("DEBUG: generate_random_image called")
Logger.debug('MainScreen: generate_random_image called')
try:
Logger.debug('Starting image generation/capture...')
if is_android():
# Android camera functionality
from android.permissions import request_permissions, Permission
request_permissions([
Permission.CAMERA,
Permission.WRITE_EXTERNAL_STORAGE,
Permission.READ_EXTERNAL_STORAGE
])
from plyer import camera
# Filename for photo
img_path = os.path.join(IMAGE_DIR, f"camera_image_{random.randint(1, 10000)}.jpg")
def on_camera_status(status):
"""Callback for camera status"""
if status == 'completed':
Logger.debug(f'Photo successfully taken: {img_path}')
# Add image as new card
self.image_count += 1
self.add_photo_card(img_path, self.image_count)
Logger.debug('Camera image successfully added')
else:
Logger.error(f'Camera status: {status}')
# Start camera
try:
camera.take_picture(
filename=img_path,
callback=on_camera_status
)
Logger.debug(f'Camera started with target file: {img_path}')
except Exception as camera_error:
Logger.error(f'Error starting camera: {str(camera_error)}')
Logger.error(traceback.format_exc())
else:
# Desktop: Generate random image
Logger.debug('Desktop mode: Generating random image...')
width = min(Window.width, 1080)
height = int(width * 16 / 9)
# Create 3D array for image
img_array = np.zeros((height, width, 3), dtype=np.uint8)
# Generate random color
r = random.randint(0, 255)
g = random.randint(0, 255)
b = random.randint(0, 255)
Logger.debug(f'Generated color RGB: ({r}, {g}, {b})')
# Fill array with color
img_array[:, :, 0] = r # Red channel
img_array[:, :, 1] = g # Green channel
img_array[:, :, 2] = b # Blue channel
# Convert to PIL Image
img = PILImage.fromarray(img_array, 'RGB')
img_path = os.path.join(IMAGE_DIR, f"random_image_{random.randint(1, 10000)}.png")
Logger.debug(f'Saving image to: {img_path}')
# Save with full quality
img.save(img_path, format='PNG', quality=100)
self.image_count += 1
self.add_photo_card(img_path, self.image_count)
Logger.debug(f'Image successfully generated and added with RGB: ({r}, {g}, {b})')
except Exception as e:
Logger.error(f'Error generating/capturing image: {str(e)}')
Logger.error(traceback.format_exc())
def add_photo_card(self, photo_path, index):
"""Add a new PhotoCard"""
try:
Logger.debug(f'Adding PhotoCard with image: {photo_path} and index: {index}')
card = PhotoCard(photo_path=photo_path, index=index)
self.image_layout.add_widget(card)
Logger.debug('PhotoCard successfully added')
except Exception as e:
Logger.error(f'Error adding PhotoCard: {str(e)}')
Logger.error(traceback.format_exc())
def stop_app(self, instance):
"""Stop the app"""
print("DEBUG: stop_app called")
Logger.debug('MainScreen: stop_app called')
self.clear_images()
Logger.debug('MainScreen: Images cleared')
MDApp.get_running_app().stop()
Logger.debug('MainScreen: App stopping')
def clear_images(self):
"""Clear all images"""
for widget in self.image_layout.children[:]:
self.image_layout.remove_widget(widget)
# Delete all images in directory
for file in os.listdir(IMAGE_DIR):
file_path = os.path.join(IMAGE_DIR, file)
if os.path.isfile(file_path):
os.remove(file_path)
def toggle_theme(self, *args):
"""Toggle between light and dark theme"""
app = MDApp.get_running_app()
app.theme_cls.theme_style = (
"Light" if app.theme_cls.theme_style == "Dark" else "Dark"
)
self.dropdown_menu.dismiss()
Logger.debug(f'Theme switched to: {app.theme_cls.theme_style}')
class TestApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.theme_cls.primary_palette = "Olive"
self.theme_cls.material_style = "M3"
Logger.debug('App: Initialization started')
# Set orientation at startup
if is_android():
try:
from jnius import autoclass
activity = autoclass('org.kivy.android.PythonActivity').mActivity
activity.setRequestedOrientation(1)
Window.rotation = 0
Window.orientation = 'portrait'
Logger.debug('Android mode: Portrait orientation forced in __init__')
except Exception as e:
Logger.error(f'Error setting orientation: {str(e)}')
def build(self):
try:
Logger.debug('App: Build method started')
if is_android():
Window.rotation = 0
Window.orientation = 'portrait'
Logger.debug('Android mode: Portrait orientation confirmed in build')
else:
Window.size = (480, 1066)
Logger.debug('Desktop mode: Window size set')
screen = MainScreen()
# Set screen background color
screen.md_bg_color = self.theme_cls.backgroundColor
Logger.debug('App: MainScreen created')
return screen
except Exception as e:
Logger.critical(f'App: Critical error in build(): {str(e)}')
Logger.critical(traceback.format_exc())
return None
def on_start(self):
try:
Logger.debug('App: on_start() called')
if is_android():
Logger.debug('Android mode: Requesting permissions')
from android.permissions import request_permissions, Permission
# Log detailed platform information
from kivy import platform
Logger.debug(f'Platform: {platform}')
Logger.debug(f'Image directory: {IMAGE_DIR}')
request_permissions([
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE"
])
super().on_start()
except Exception as e:
Logger.critical(f'App: Error in on_start(): {str(e)}')
Logger.critical(traceback.format_exc())
def on_pause(self):
Logger.debug('App: on_pause() called')
return True
def on_resume(self):
Logger.debug('App: on_resume() called')
pass
def on_stop(self):
Logger.debug('App: on_stop() called')
pass
# Main program
if __name__ == "__main__":
TestApp().run()