actorcat

Grocery list

May 30th, 2026 (edited)
106
0
Never
7
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 34.56 KB | None | 0 0
  1. #  Buildozer.spec at bottom of script
  2.  
  3.  
  4. import json
  5. import os
  6. import re
  7.  
  8. from kivy.app import App
  9. from kivy.clock import Clock
  10. from kivy.core.clipboard import Clipboard
  11. from kivy.core.window import Window
  12. from kivy.lang import Builder
  13. from kivy.metrics import dp
  14. from kivy.properties import BooleanProperty
  15. from kivy.uix.boxlayout import BoxLayout
  16. from kivy.uix.button import Button
  17. from kivy.uix.label import Label
  18. from kivy.uix.popup import Popup
  19. from kivy.uix.spinner import Spinner
  20. from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem
  21. from kivy.uix.textinput import TextInput
  22. from kivy.uix.widget import Widget
  23. from kivy.utils import platform
  24. from kivymd.app import MDApp
  25. from kivymd.uix.boxlayout import MDBoxLayout
  26. from kivymd.uix.button import MDIconButton
  27.  
  28.  
  29. GROCERY_KV = '''
  30. MDScreen:
  31.     md_bg_color: 0.97, 0.97, 0.97, 1
  32.  
  33.     MDBoxLayout:
  34.         orientation: "vertical"
  35.         padding: 16
  36.         spacing: 16
  37.         md_bg_color: 0.97, 0.97, 0.97, 1
  38.  
  39.         MDBoxLayout:
  40.             spacing: 8
  41.             size_hint_y: None
  42.             height: self.minimum_height
  43.             md_bg_color: 0.97, 0.97, 0.97, 1
  44.  
  45.             MDTextField:
  46.                 id: item_input
  47.                 hint_text: "Add grocery item"
  48.                 mode: "rectangle"
  49.                 font_size: "28sp"
  50.                 text_color_normal: 0, 0, 0, 1
  51.                 text_color_focus: 0, 0, 0, 1
  52.                 line_color_normal: 0, 0, 0, 1
  53.                 line_color_focus: 0, 0, 0, 1
  54.                 hint_text_color_normal: 0.3, 0.3, 0.3, 1
  55.                 hint_text_color_focus: 0.2, 0.2, 0.2, 1
  56.                 on_text_validate: app.add_grocery_item()
  57.  
  58.             MDIconButton:
  59.                 icon: "plus"
  60.                 theme_icon_color: "Custom"
  61.                 icon_color: 0, 0, 0, 1
  62.                 on_release: app.add_grocery_item()
  63.  
  64.             MDIconButton:
  65.                 icon: "sort-alphabetical-ascending"
  66.                 theme_icon_color: "Custom"
  67.                 icon_color: 0, 0, 0, 1
  68.                 on_release: app.sort_grocery_items()
  69.  
  70.         MDSeparator:
  71.             height: "1dp"
  72.             color: 0, 0, 0, 1
  73.  
  74.         ScrollView:
  75.             MDBoxLayout:
  76.                 id: grocery_list
  77.                 orientation: "vertical"
  78.                 adaptive_height: True
  79. '''
  80.  
  81.  
  82. class BlackDivider(Widget):
  83.     def __init__(self, **kwargs):
  84.         super().__init__(size_hint_y=None, height=1, **kwargs)
  85.         from kivy.graphics import Color, Rectangle
  86.  
  87.         with self.canvas:
  88.             Color(0, 0, 0, 1)
  89.             self.rect = Rectangle(pos=self.pos, size=self.size)
  90.         self.bind(pos=self._update_rect, size=self._update_rect)
  91.  
  92.     def _update_rect(self, *_):
  93.         self.rect.pos = self.pos
  94.         self.rect.size = self.size
  95.  
  96.  
  97. class NoteNamePopup(Popup):
  98.     def __init__(self, save_callback, **kwargs):
  99.         super().__init__(**kwargs)
  100.         self.title = "Create New Note"
  101.         self.size_hint = (0.85, 0.35)
  102.         self.auto_dismiss = False
  103.         self.save_callback = save_callback
  104.  
  105.         layout = BoxLayout(orientation="vertical", spacing=dp(10), padding=dp(12))
  106.         self.name_input = TextInput(
  107.             hint_text="Note name",
  108.             multiline=False,
  109.             size_hint_y=None,
  110.             height=dp(44),
  111.         )
  112.  
  113.         btn_row = BoxLayout(orientation="horizontal", size_hint_y=None, height=dp(44), spacing=dp(8))
  114.         cancel_btn = Button(text="Cancel")
  115.         save_btn = Button(text="Save")
  116.  
  117.         cancel_btn.bind(on_release=lambda *_: self.dismiss())
  118.         save_btn.bind(on_release=self._on_save)
  119.         self.name_input.bind(on_text_validate=self._on_save)
  120.  
  121.         btn_row.add_widget(cancel_btn)
  122.         btn_row.add_widget(save_btn)
  123.  
  124.         layout.add_widget(self.name_input)
  125.         layout.add_widget(btn_row)
  126.  
  127.         self.content = layout
  128.  
  129.     def _on_save(self, *_):
  130.         name = self.name_input.text.strip()
  131.         if name:
  132.             self.save_callback(name)
  133.             self.dismiss()
  134.  
  135.  
  136. class GroceryItem(MDBoxLayout):
  137.     crossed = BooleanProperty(False)
  138.  
  139.     def __init__(self, text, crossed=False, **kwargs):
  140.         super().__init__(orientation="horizontal", size_hint_y=None, height=dp(50), **kwargs)
  141.         self.label = Label(
  142.             text=text,
  143.             halign="left",
  144.             valign="middle",
  145.             markup=False,
  146.             size_hint_x=1,
  147.             font_size=dp(28),
  148.             color=(0, 0, 0, 1),
  149.             size_hint_y=None,
  150.             height=dp(40),
  151.             text_size=(1, None),
  152.         )
  153.         self.label.bind(width=lambda instance, value: setattr(instance, "text_size", (value, None)))
  154.         self.delete_btn = MDIconButton(
  155.             icon="delete",
  156.             theme_icon_color="Custom",
  157.             icon_color=(0, 0, 0, 1),
  158.             on_release=self.remove_item,
  159.             size_hint_x=None,
  160.             size_hint_y=None,
  161.             height=dp(96),
  162.         )
  163.         self.add_widget(self.label)
  164.         self.add_widget(self.delete_btn)
  165.         self.label.bind(on_touch_down=self.on_label_touch)
  166.         self.crossed = crossed
  167.         self._cross_line = None
  168.         self.label.bind(pos=self._update_cross_line, size=self._update_cross_line)
  169.         if self.crossed:
  170.             self._draw_cross_line()
  171.  
  172.     def on_label_touch(self, _instance, touch):
  173.         if self.label.collide_point(*touch.pos):
  174.             self.crossed = not self.crossed
  175.             if self.crossed:
  176.                 self._draw_cross_line()
  177.             else:
  178.                 self._remove_cross_line()
  179.             app = App.get_running_app()
  180.             if hasattr(app, "save_grocery_list"):
  181.                 app.save_grocery_list()
  182.  
  183.     def _draw_cross_line(self, *_):
  184.         from kivy.graphics import Color, Line
  185.  
  186.         self._remove_cross_line()
  187.         with self.label.canvas.after:
  188.             Color(1, 0, 0, 1)
  189.             self._cross_line = Line(points=self._get_cross_points(), width=3)
  190.  
  191.     def _remove_cross_line(self, *_):
  192.         if self._cross_line:
  193.             self.label.canvas.after.remove(self._cross_line)
  194.             self._cross_line = None
  195.  
  196.     def _update_cross_line(self, *_):
  197.         if self.crossed:
  198.             self._draw_cross_line()
  199.  
  200.     def _get_cross_points(self):
  201.         x, y = self.label.pos
  202.         w, h = self.label.size
  203.         return [x, y + h / 2, x + w, y + h / 2]
  204.  
  205.     def remove_item(self, *_):
  206.         if self.parent:
  207.             self.parent.remove_widget(self)
  208.             app = App.get_running_app()
  209.             if hasattr(app, "save_grocery_list"):
  210.                 app.save_grocery_list()
  211.  
  212.  
  213. class CombinedApp(MDApp):
  214.     NOTES_DIR = "notes"
  215.     META_FILE = "notes_meta.json"
  216.     LIST_FILE = "grocery_list.txt"
  217.     MIN_NOTE_FONT_SIZE = 36
  218.     MAX_NOTE_FONT_SIZE = 72
  219.  
  220.     def __init__(self, **kwargs):
  221.         super().__init__(**kwargs)
  222.         self.notes = {}
  223.         self.note_files = {}
  224.         self.current_note = ""
  225.         self.text_input = None
  226.         self.note_font_size = self.MIN_NOTE_FONT_SIZE
  227.         self.note_spinner = None
  228.         self._updating_text = False
  229.         self.grocery_root = None
  230.         self._grocery_tab = None
  231.  
  232.     def build(self):
  233.         self.theme_cls.theme_style = "Light"
  234.         self.theme_cls.primary_palette = "BlueGray"
  235.         try:
  236.             # Keep manual window sizing for desktop only.
  237.             if platform not in ("android", "ios"):
  238.                 Window.size = (500, 760)
  239.                 Window.minimum_width = 320
  240.                 Window.minimum_height = 520
  241.             Window.allow_screensaver = True
  242.         except Exception:
  243.             pass
  244.  
  245.         Window.bind(on_key_down=self.on_key_down)
  246.         self.load_notes()
  247.  
  248.         root = TabbedPanel(do_default_tab=False, tab_pos="top_mid")
  249.  
  250.         notes_tab = TabbedPanelItem(text="Notes")
  251.         notes_tab.add_widget(self._build_notes_layout())
  252.         root.add_widget(notes_tab)
  253.  
  254.         grocery_tab = TabbedPanelItem(text="Groceries")
  255.         self.grocery_root = Builder.load_string(GROCERY_KV)
  256.         grocery_tab.add_widget(self.grocery_root)
  257.         root.add_widget(grocery_tab)
  258.         self._grocery_tab = grocery_tab
  259.  
  260.         root.default_tab = grocery_tab
  261.         self.refresh_spinner()
  262.         self.load_current_note_to_editor()
  263.  
  264.         return root
  265.  
  266.     def on_start(self):
  267.         # Let first frame render before switching tabs/loading long lists on Android.
  268.         Clock.schedule_once(self._finish_startup, 0.15)
  269.  
  270.     def _finish_startup(self, *_):
  271.         if self.root and self._grocery_tab:
  272.             self.root.switch_to(self._grocery_tab)
  273.         self.load_grocery_list()
  274.  
  275.     def _build_notes_layout(self):
  276.         root = BoxLayout(orientation="vertical", spacing=dp(10), padding=dp(12))
  277.  
  278.         top_bar = BoxLayout(orientation="horizontal", size_hint_y=None, height=dp(44), spacing=dp(8))
  279.  
  280.         self.note_spinner = Spinner(text="", values=[], size_hint_x=0.62)
  281.         self.note_spinner.bind(text=self.on_select_note)
  282.  
  283.         add_btn = Button(text="New", size_hint_x=0.19)
  284.         delete_btn = Button(text="Delete", size_hint_x=0.19)
  285.         add_btn.bind(on_release=self.open_add_popup)
  286.         delete_btn.bind(on_release=self.delete_current_note)
  287.  
  288.         top_bar.add_widget(self.note_spinner)
  289.         top_bar.add_widget(add_btn)
  290.         top_bar.add_widget(delete_btn)
  291.  
  292.         edit_bar = BoxLayout(orientation="horizontal", size_hint_y=None, height=dp(40), spacing=dp(8))
  293.         paste_btn = Button(text="Paste")
  294.         copy_btn = Button(text="Copy")
  295.         shrink_btn = Button(text="A-")
  296.         grow_btn = Button(text="A+")
  297.         paste_btn.bind(on_release=self.paste_from_clipboard)
  298.         copy_btn.bind(on_release=self.copy_to_clipboard)
  299.         shrink_btn.bind(on_release=self.decrease_note_font_size)
  300.         grow_btn.bind(on_release=self.increase_note_font_size)
  301.         edit_bar.add_widget(paste_btn)
  302.         edit_bar.add_widget(copy_btn)
  303.         edit_bar.add_widget(shrink_btn)
  304.         edit_bar.add_widget(grow_btn)
  305.  
  306.         self.text_input = TextInput(hint_text="Write your note here...", multiline=True, font_size=self.note_font_size)
  307.         self.text_input.bind(text=self.on_text_changed)
  308.  
  309.         root.add_widget(top_bar)
  310.         root.add_widget(edit_bar)
  311.         root.add_widget(self.text_input)
  312.         return root
  313.  
  314.     def _default_notes(self):
  315.         return {"My Note": ""}, "My Note"
  316.  
  317.     def _sanitize_note_name(self, name):
  318.         clean = re.sub(r'[<>:"/\\|?*]', "_", name.strip())
  319.         return clean or "My Note"
  320.  
  321.     def _note_path(self, note_name):
  322.         filename = self.note_files.get(note_name, f"{note_name}.txt")
  323.         return os.path.join(self.NOTES_DIR, filename)
  324.  
  325.     def _generate_unique_name(self, base_name):
  326.         if base_name not in self.notes:
  327.             return base_name
  328.         index = 2
  329.         while f"{base_name} {index}" in self.notes:
  330.             index += 1
  331.         return f"{base_name} {index}"
  332.  
  333.     def load_notes(self):
  334.         os.makedirs(self.NOTES_DIR, exist_ok=True)
  335.         self.notes = {}
  336.         self.note_files = {}
  337.  
  338.         for filename in sorted(os.listdir(self.NOTES_DIR)):
  339.             if not filename.lower().endswith(".txt"):
  340.                 continue
  341.             note_name = os.path.splitext(filename)[0]
  342.             file_path = os.path.join(self.NOTES_DIR, filename)
  343.             try:
  344.                 with open(file_path, "r", encoding="utf-8") as file:
  345.                     content = file.read()
  346.             except OSError:
  347.                 content = ""
  348.             self.notes[note_name] = content
  349.             self.note_files[note_name] = filename
  350.  
  351.         if not self.notes:
  352.             default_notes, default_selected = self._default_notes()
  353.             self.notes = default_notes
  354.             self.current_note = default_selected
  355.             self.note_files = {default_selected: f"{default_selected}.txt"}
  356.             self.save_notes()
  357.             return
  358.  
  359.         selected = ""
  360.         saved_font_size = None
  361.         if os.path.exists(self.META_FILE):
  362.             try:
  363.                 with open(self.META_FILE, "r", encoding="utf-8") as file:
  364.                     meta = json.load(file)
  365.                 selected = meta.get("selected", "")
  366.                 saved_font_size = meta.get("font_size")
  367.             except (json.JSONDecodeError, OSError):
  368.                 selected = ""
  369.                 saved_font_size = None
  370.  
  371.         if selected in self.notes:
  372.             self.current_note = selected
  373.         else:
  374.             self.current_note = next(iter(self.notes))
  375.  
  376.         if isinstance(saved_font_size, (int, float)):
  377.             self.note_font_size = max(
  378.                 self.MIN_NOTE_FONT_SIZE,
  379.                 min(self.MAX_NOTE_FONT_SIZE, int(saved_font_size)),
  380.             )
  381.  
  382.     def save_notes(self):
  383.         os.makedirs(self.NOTES_DIR, exist_ok=True)
  384.  
  385.         for note_name, content in self.notes.items():
  386.             note_path = self._note_path(note_name)
  387.             with open(note_path, "w", encoding="utf-8") as file:
  388.                 file.write(content)
  389.  
  390.         meta_payload = {
  391.             "selected": self.current_note,
  392.             "font_size": self.note_font_size,
  393.         }
  394.         with open(self.META_FILE, "w", encoding="utf-8") as file:
  395.             json.dump(meta_payload, file, ensure_ascii=False, indent=2)
  396.  
  397.     def refresh_spinner(self):
  398.         if not self.note_spinner:
  399.             return
  400.         names = list(self.notes.keys())
  401.         self.note_spinner.values = names
  402.         self.note_spinner.text = self.current_note
  403.  
  404.     def load_current_note_to_editor(self):
  405.         if not self.text_input:
  406.             return
  407.         self._updating_text = True
  408.         self.text_input.text = self.notes.get(self.current_note, "")
  409.  
  410.         def unlock(*_):
  411.             # Keep note view anchored at the beginning when switching/opening notes.
  412.             self.text_input.cursor = (0, 0)
  413.             self.text_input.scroll_x = 0
  414.             self.text_input.scroll_y = 0
  415.             self._updating_text = False
  416.  
  417.         Clock.schedule_once(unlock, 0)
  418.  
  419.     def on_select_note(self, _spinner, selected_name):
  420.         if selected_name not in self.notes:
  421.             return
  422.         self.current_note = selected_name
  423.         self.load_current_note_to_editor()
  424.         self.save_notes()
  425.  
  426.     def on_text_changed(self, _instance, value):
  427.         if self._updating_text or self.current_note not in self.notes:
  428.             return
  429.         self.notes[self.current_note] = value
  430.         note_path = self._note_path(self.current_note)
  431.         with open(note_path, "w", encoding="utf-8") as file:
  432.             file.write(value)
  433.  
  434.     def paste_from_clipboard(self, *_):
  435.         clipboard_text = Clipboard.paste() or ""
  436.         if not clipboard_text:
  437.             return
  438.         self.text_input.insert_text(clipboard_text)
  439.  
  440.     def copy_to_clipboard(self, *_):
  441.         Clipboard.copy(self.text_input.text)
  442.  
  443.     def _set_note_font_size(self, new_size):
  444.         self.note_font_size = max(self.MIN_NOTE_FONT_SIZE, min(self.MAX_NOTE_FONT_SIZE, new_size))
  445.         if self.text_input:
  446.             text_input = self.text_input
  447.             prev_cursor = text_input.cursor
  448.             prev_scroll_x = text_input.scroll_x
  449.             prev_scroll_y = text_input.scroll_y
  450.             text_input.font_size = self.note_font_size
  451.  
  452.             # Kivy reflows text on resize and may auto-scroll to cursor; restore viewport.
  453.             def restore_view(*_):
  454.                 if self.text_input is text_input:
  455.                     text_input.cursor = prev_cursor
  456.                     text_input.scroll_x = prev_scroll_x
  457.                     text_input.scroll_y = prev_scroll_y
  458.  
  459.             Clock.schedule_once(restore_view, 0)
  460.             Clock.schedule_once(restore_view, 0.05)
  461.         self.save_notes()
  462.  
  463.     def increase_note_font_size(self, *_):
  464.         self._set_note_font_size(self.note_font_size + 2)
  465.  
  466.     def decrease_note_font_size(self, *_):
  467.         self._set_note_font_size(self.note_font_size - 2)
  468.  
  469.     def on_key_down(self, _window, key, _scancode, _text, modifiers):
  470.         current_tab = self.root.current_tab if self.root else None
  471.         if "ctrl" in modifiers and key == 118:
  472.             if current_tab and current_tab.text == "Groceries":
  473.                 self.paste_grocery_input()
  474.             else:
  475.                 self.paste_from_clipboard()
  476.             return True
  477.         if "ctrl" in modifiers and key == 99:
  478.             if current_tab and current_tab.text == "Groceries":
  479.                 self.copy_grocery_list()
  480.             else:
  481.                 self.copy_to_clipboard()
  482.             return True
  483.         return False
  484.  
  485.     def open_add_popup(self, *_):
  486.         popup = NoteNamePopup(save_callback=self.create_note)
  487.         popup.open()
  488.  
  489.     def create_note(self, note_name):
  490.         clean_name = self._sanitize_note_name(note_name)
  491.         final_name = self._generate_unique_name(clean_name)
  492.         if final_name not in self.notes:
  493.             self.notes[final_name] = ""
  494.             self.note_files[final_name] = f"{final_name}.txt"
  495.             self.notes = {name: self.notes[name] for name in sorted(self.notes.keys())}
  496.         self.current_note = final_name
  497.         self.refresh_spinner()
  498.         self.load_current_note_to_editor()
  499.         self.save_notes()
  500.  
  501.     def delete_current_note(self, *_):
  502.         if self.current_note not in self.notes:
  503.             return
  504.  
  505.         note_path = self._note_path(self.current_note)
  506.         if os.path.exists(note_path):
  507.             try:
  508.                 os.remove(note_path)
  509.             except OSError:
  510.                 pass
  511.  
  512.         if len(self.notes) == 1:
  513.             self.notes, self.current_note = self._default_notes()
  514.             self.note_files = {self.current_note: f"{self.current_note}.txt"}
  515.         else:
  516.             del self.note_files[self.current_note]
  517.             del self.notes[self.current_note]
  518.             self.current_note = next(iter(self.notes))
  519.  
  520.         self.refresh_spinner()
  521.         self.load_current_note_to_editor()
  522.         self.save_notes()
  523.  
  524.     def _grocery_widgets(self):
  525.         if not self.grocery_root:
  526.             return None, None
  527.         return self.grocery_root.ids.item_input, self.grocery_root.ids.grocery_list
  528.  
  529.     def add_grocery_item(self):
  530.         item_input, grocery_list = self._grocery_widgets()
  531.         if not item_input or not grocery_list:
  532.             return
  533.  
  534.         item_text = item_input.text.strip()
  535.         if not item_text:
  536.             return
  537.  
  538.         grocery_list.add_widget(BlackDivider())
  539.         grocery_list.add_widget(GroceryItem(item_text))
  540.         grocery_list.add_widget(BlackDivider())
  541.         item_input.text = ""
  542.         self.sort_grocery_items()
  543.         self.save_grocery_list()
  544.  
  545.     def paste_grocery_input(self, *_):
  546.         item_input, _grocery_list = self._grocery_widgets()
  547.         if not item_input:
  548.             return
  549.  
  550.         clipboard_text = Clipboard.paste() or ""
  551.         if not clipboard_text:
  552.             return
  553.  
  554.         item_input.text = clipboard_text.strip()
  555.  
  556.     def copy_grocery_list(self, *_):
  557.         _item_input, grocery_list = self._grocery_widgets()
  558.         if not grocery_list:
  559.             return
  560.  
  561.         items = [item for item in grocery_list.children if isinstance(item, GroceryItem)][::-1]
  562.         Clipboard.copy("\n".join(item.label.text for item in items))
  563.  
  564.     def sort_grocery_items(self):
  565.         _item_input, grocery_list = self._grocery_widgets()
  566.         if not grocery_list:
  567.             return
  568.  
  569.         def clean_text(item):
  570.             return item.label.text.strip().lower()
  571.  
  572.         children = grocery_list.children[:]
  573.         items = [item for item in children if isinstance(item, GroceryItem)]
  574.         uncrossed = sorted((item for item in items if not item.crossed), key=clean_text)
  575.         crossed = sorted((item for item in items if item.crossed), key=clean_text)
  576.  
  577.         for child in children:
  578.             grocery_list.remove_widget(child)
  579.  
  580.         for item in uncrossed + crossed:
  581.             grocery_list.add_widget(BlackDivider())
  582.             grocery_list.add_widget(item)
  583.             grocery_list.add_widget(BlackDivider())
  584.  
  585.     def save_grocery_list(self):
  586.         _item_input, grocery_list = self._grocery_widgets()
  587.         if not grocery_list:
  588.             return
  589.  
  590.         items = [item for item in grocery_list.children if isinstance(item, GroceryItem)][::-1]
  591.         with open(self.LIST_FILE, "w", encoding="utf-8") as file:
  592.             for item in items:
  593.                 file.write(f"{int(item.crossed)}\t{item.label.text}\n")
  594.  
  595.     def load_grocery_list(self):
  596.         _item_input, grocery_list = self._grocery_widgets()
  597.         if not grocery_list:
  598.             return
  599.  
  600.         grocery_list.clear_widgets()
  601.         if not os.path.exists(self.LIST_FILE):
  602.             return
  603.  
  604.         items = []
  605.         with open(self.LIST_FILE, "r", encoding="utf-8") as file:
  606.             for line in file:
  607.                 parts = line.rstrip("\n").split("\t", 1)
  608.                 if len(parts) != 2:
  609.                     continue
  610.                 crossed, text = parts
  611.                 items.append(GroceryItem(text, crossed=bool(int(crossed))))
  612.  
  613.         for item in items:
  614.             grocery_list.add_widget(BlackDivider())
  615.             grocery_list.add_widget(item)
  616.             grocery_list.add_widget(BlackDivider())
  617.  
  618.         self.sort_grocery_items()
  619.  
  620.  
  621. if __name__ == "__main__":
  622.     CombinedApp().run()
  623.  
  624.  
  625. Buildozer.spec
  626.  
  627. [app]
  628.  
  629. # (str) Title of your application
  630. title = Grocery
  631.  
  632. # (str) Package name
  633. package.name = kivyapp
  634.  
  635. # (str) Package domain (needed for android/ios packaging)
  636. package.domain = org.test
  637.  
  638. # (str) Source code where the main.py live
  639. source.dir = .
  640.  
  641. # (list) Source files to include (let empty to include all the files)
  642. source.include_exts = py,png,jpg,kv,atlas
  643.  
  644. # (list) List of inclusions using pattern matching
  645. #source.include_patterns = assets/*,images/*.png
  646.  
  647. # (list) Source files to exclude (let empty to not exclude anything)
  648. #source.exclude_exts = spec
  649.  
  650. # (list) List of directory to exclude (let empty to not exclude anything)
  651. #source.exclude_dirs = tests, bin, venv
  652.  
  653. # (list) List of exclusions using pattern matching
  654. # Do not prefix with './'
  655. #source.exclude_patterns = license,images/*/*.jpg
  656.  
  657. # (str) Application versioning (method 1)
  658. version = 0.1
  659.  
  660. # (str) Application versioning (method 2)
  661. # version.regex = __version__ = ['"](.*)['"]
  662. # version.filename = %(source.dir)s/main.py
  663.  
  664. # (list) Application requirements
  665. # comma separated e.g. requirements = sqlite3,kivy
  666.  
  667.  
  668. # (str) Custom source folders for requirements
  669. # Sets custom source for any requirements with recipes
  670. # requirements.source.kivy = ../../kivy
  671. requirements = python3,kivy,kivymd
  672.  
  673. # (str) Presplash of the application
  674. #presplash.filename = %(source.dir)s/data/presplash.png
  675.  
  676. # (str) Icon of the application
  677. icon.filename = %(source.dir)s/icon.png
  678.  
  679. # (list) Supported orientations
  680. # Valid options are: landscape, portrait, portrait-reverse or landscape-reverse
  681. orientation = portrait
  682.  
  683. # (list) List of service to declare
  684. #services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY
  685.  
  686. #
  687. # OSX Specific
  688. #
  689.  
  690. #
  691. # author = © Copyright Info
  692.  
  693. # change the major version of python used by the app
  694. osx.python_version = 3
  695.  
  696. # Kivy version to use
  697. osx.kivy_version = 1.9.1
  698.  
  699. #
  700. # Android specific
  701. #
  702.  
  703. # (bool) Indicate if the application should be fullscreen or not
  704. fullscreen = 1
  705.  
  706. # (string) Presplash background color (for android toolchain)
  707. # Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
  708. # red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
  709. # darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
  710. # olive, purple, silver, teal.
  711. #android.presplash_color = #FFFFFF
  712.  
  713. # (string) Presplash animation using Lottie format.
  714. # see https://lottiefiles.com/ for examples and https://airbnb.design/lottie/
  715. # for general documentation.
  716. # Lottie files can be created using various tools, like Adobe After Effect or Synfig.
  717. #android.presplash_lottie = "path/to/lottie/file.json"
  718.  
  719. # (str) Adaptive icon of the application (used if Android API level is 26+ at runtime)
  720. #icon.adaptive_foreground.filename = %(source.dir)s/data/icon_fg.png
  721. #icon.adaptive_background.filename = %(source.dir)s/data/icon_bg.png
  722.  
  723. #android.permissions = WAKE_LOCK
  724. # (See https://python-for-android.readthedocs.io/en/latest/buildoptions/#build-options-1 for all the supported syntaxes and properties)
  725. #android.permissions = android.permission.INTERNET, (name=android.permission.WRITE_EXTERNAL_STORAGE;maxSdkVersion=18)
  726.  
  727. # (list) features (adds uses-feature -tags to manifest)
  728. #android.features = android.hardware.usb.host
  729.  
  730. # (int) Target Android API, should be as high as possible.
  731. android.api = 33
  732.  
  733. # (int) Minimum API your APK / AAB will support.
  734. android.minapi = 21
  735. android.ndk = 25b
  736.  
  737. # (int) Android SDK version to use
  738. #android.sdk = 20
  739.  
  740. # (str) Android NDK version to use
  741. #android.ndk = 25b
  742.  
  743. # (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
  744. #android.ndk_api = 21
  745.  
  746. # (bool) Use --private data storage (True) or --dir public storage (False)
  747. #android.private_storage = True
  748.  
  749. # (str) Android NDK directory (if empty, it will be automatically downloaded.)
  750. #android.ndk_path =
  751.  
  752. # (str) Android SDK directory (if empty, it will be automatically downloaded.)
  753. #android.sdk_path =
  754.  
  755. # (str) ANT directory (if empty, it will be automatically downloaded.)
  756. #android.ant_path =
  757.  
  758. # (bool) If True, then skip trying to update the Android sdk
  759. # This can be useful to avoid excess Internet downloads or save time
  760. # when an update is due and you just want to test/build your package
  761. # android.skip_update = False
  762.  
  763. # (bool) If True, then automatically accept SDK license
  764. # agreements. This is intended for automation only. If set to False,
  765. # the default, you will be shown the license when first running
  766. # buildozer.
  767. # android.accept_sdk_license = False
  768.  
  769. # (str) Android entry point, default is ok for Kivy-based app
  770. #android.entrypoint = org.kivy.android.PythonActivity
  771.  
  772. # (str) Full name including package path of the Java class that implements Android Activity
  773. # use that parameter together with android.entrypoint to set custom Java class instead of PythonActivity
  774. #android.activity_class_name = org.kivy.android.PythonActivity
  775.  
  776. # (str) Extra xml to write directly inside the <manifest> element of AndroidManifest.xml
  777. # use that parameter to provide a filename from where to load your custom XML code
  778. #android.extra_manifest_xml = ./src/android/extra_manifest.xml
  779.  
  780. # (str) Extra xml to write directly inside the <manifest><application> tag of AndroidManifest.xml
  781. # use that parameter to provide a filename from where to load your custom XML arguments:
  782. #android.extra_manifest_application_arguments = ./src/android/extra_manifest_application_arguments.xml
  783.  
  784. # (str) Full name including package path of the Java class that implements Python Service
  785. # use that parameter to set custom Java class which extends PythonService
  786. #android.service_class_name = org.kivy.android.PythonService
  787.  
  788. # (str) Android app theme, default is ok for Kivy-based app
  789. # android.apptheme = "@android:style/Theme.NoTitleBar"
  790.  
  791. # (list) Pattern to whitelist for the whole project
  792. #android.whitelist =
  793.  
  794. # (str) Path to a custom whitelist file
  795. #android.whitelist_src =
  796.  
  797. # (str) Path to a custom blacklist file
  798. #android.blacklist_src =
  799.  
  800. # (list) List of Java .jar files to add to the libs so that pyjnius can access
  801. # their classes. Don't add jars that you do not need, since extra jars can slow
  802. # down the build process. Allows wildcards matching, for example:
  803. # OUYA-ODK/libs/*.jar
  804. #android.add_jars = foo.jar,bar.jar,path/to/more/*.jar
  805.  
  806. # (list) List of Java files to add to the android project (can be java or a
  807. # directory containing the files)
  808. #android.add_src =
  809.  
  810. # (list) Android AAR archives to add
  811. #android.add_aars =
  812.  
  813. # (list) Put these files or directories in the apk assets directory.
  814. # Either form may be used, and assets need not be in 'source.include_exts'.
  815. # 1) android.add_assets = source_asset_relative_path
  816. # 2) android.add_assets = source_asset_path:destination_asset_relative_path
  817. #android.add_assets =
  818.  
  819. # (list) Put these files or directories in the apk res directory.
  820. # The option may be used in three ways, the value may contain one or zero ':'
  821. # Some examples:
  822. # 1) A file to add to resources, legal resource names contain ['a-z','0-9','_']
  823. # android.add_resources = my_icons/all-inclusive.png:drawable/all_inclusive.png
  824. # 2) A directory, here  'legal_icons' must contain resources of one kind
  825. # android.add_resources = legal_icons:drawable
  826. # 3) A directory, here 'legal_resources' must contain one or more directories,
  827. # each of a resource kind:  drawable, xml, etc...
  828. # android.add_resources = legal_resources
  829. #android.add_resources =
  830.  
  831. # (list) Gradle dependencies to add
  832. #android.gradle_dependencies =
  833.  
  834. # (bool) Enable AndroidX support. Enable when 'android.gradle_dependencies'
  835. # contains an 'androidx' package, or any package from Kotlin source.
  836. # android.enable_androidx requires android.api >= 28
  837. #android.enable_androidx = True
  838.  
  839. # (list) add java compile options
  840. # this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
  841. # see https://developer.android.com/studio/write/java8-support for further information
  842. # android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"
  843.  
  844. # (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
  845. # please enclose in double quotes
  846. # e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
  847. #android.add_gradle_repositories =
  848.  
  849. # (list) packaging options to add
  850. # see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
  851. # can be necessary to solve conflicts in gradle_dependencies
  852. # please enclose in double quotes
  853. # e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
  854. #android.add_packaging_options =
  855.  
  856. # (list) Java classes to add as activities to the manifest.
  857. #android.add_activities = com.example.ExampleActivity
  858.  
  859. # (str) OUYA Console category. Should be one of GAME or APP
  860. # If you leave this blank, OUYA support will not be enabled
  861. #android.ouya.category = GAME
  862.  
  863. # (str) Filename of OUYA Console icon. It must be a 732x412 png image.
  864. #android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png
  865.  
  866. # (str) XML file to include as an intent filters in <activity> tag
  867. #android.manifest.intent_filters =
  868.  
  869. # (list) Copy these files to src/main/res/xml/ (used for example with intent-filters)
  870. #android.res_xml = PATH_TO_FILE,
  871.  
  872. # (str) launchMode to set for the main activity
  873. #android.manifest.launch_mode = standard
  874.  
  875. # (str) screenOrientation to set for the main activity.
  876. # Valid values can be found at https://developer.android.com/guide/topics/manifest/activity-element
  877. #android.manifest.orientation = fullSensor
  878.  
  879. # (list) Android additional libraries to copy into libs/armeabi
  880. #android.add_libs_armeabi = libs/android/*.so
  881. #android.add_libs_armeabi_v7a = libs/android-v7/*.so
  882. #android.add_libs_arm64_v8a = libs/android-v8/*.so
  883. #android.add_libs_x86 = libs/android-x86/*.so
  884. #android.add_libs_mips = libs/android-mips/*.so
  885.  
  886. # (bool) Indicate whether the screen should stay on
  887. # Don't forget to add the WAKE_LOCK permission if you set this to True
  888. #android.wakelock = False
  889.  
  890. # (list) Android application meta-data to set (key=value format)
  891. #android.meta_data =
  892.  
  893. # (list) Android library project to add (will be added in the
  894. # project.properties automatically.)
  895. #android.library_references =
  896.  
  897. # (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
  898. #android.uses_library =
  899.  
  900. # (str) Android logcat filters to use
  901. #android.logcat_filters = *:S python:D
  902.  
  903. # (bool) Android logcat only display log for activity's pid
  904. #android.logcat_pid_only = False
  905.  
  906. # (str) Android additional adb arguments
  907. #android.adb_args = -H host.docker.internal
  908.  
  909. # (bool) Copy library instead of making a libpymodules.so
  910. #android.copy_libs = 1
  911.  
  912. # (list) The Android archs to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
  913. #android.logcat_filters = *:V
  914. # In past, was `android.arch` as we weren't supporting builds for multiple archs at the same time.
  915.  
  916. # (int) overrides automatic versionCode computation (used in build.gradle)
  917. # this is not the same as app version and should only be edited if you know what you're doing
  918. # android.numeric_version = 1
  919.  
  920. # (bool) enables Android auto backup feature (Android API >=23)
  921. android.allow_backup = True
  922.  
  923. # (str) XML file for custom backup rules (see official auto backup documentation)
  924. # android.backup_rules =
  925.  
  926. # (str) If you need to insert variables into your AndroidManifest.xml file,
  927. # you can do so with the manifestPlaceholders property.
  928. # This property takes a map of key-value pairs. (via a string)
  929. # Usage example : android.manifest_placeholders = [myCustomUrl:\"org.kivy.customurl\"]
  930. # android.manifest_placeholders = [:]
  931.  
  932. # (bool) Skip byte compile for .py files
  933. # android.no-byte-compile-python = False
  934.  
  935. # (str) The format used to package the app for release mode (aab or apk or aar).
  936. # android.release_artifact = aab
  937.  
  938. # (str) The format used to package the app for debug mode (apk or aar).
  939. # android.debug_artifact = apk
  940.  
  941. #
  942. # Python for android (p4a) specific
  943. #
  944. #android.archs = arm64-v8a
  945. # (str) python-for-android URL to use for checkout
  946. #p4a.url =
  947.  
  948. # (str) python-for-android fork to use in case if p4a.url is not specified, defaults to upstream (kivy)
  949. #p4a.fork = kivy
  950.  
  951. # (str) python-for-android branch to use, defaults to master
  952. #p4a.branch = master
  953.  
  954. # (str) python-for-android specific commit to use, defaults to HEAD, must be within p4a.branch
  955. #p4a.commit = HEAD
  956.  
  957. # (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
  958. #p4a.source_dir =
  959.  
  960. # (str) The directory in which python-for-android should look for your own build recipes (if any)
  961. #p4a.local_recipes =
  962.  
  963. # (str) Filename to the hook for p4a
  964. #p4a.hook =
  965.  
  966. # (str) Bootstrap to use for android builds
  967. #p4a.bootstrap = sdl2
  968. #p4a.branch = develop
  969.  
  970. # (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
  971. #p4a.port =
  972.  
  973. # Control passing the --use-setup-py vs --ignore-setup-py to p4a
  974. # "in the future" --use-setup-py is going to be the default behaviour in p4a, right now it is not
  975. # Setting this to false will pass --ignore-setup-py, true will pass --use-setup-py
  976. # NOTE: this is general setuptools integration, having pyproject.toml is enough, no need to generate
  977. # setup.py if you're using Poetry, but you need to add "toml" to source.include_exts.
  978. #p4a.setup_py = false
  979.  
  980. # (str) extra command line arguments to pass when invoking pythonforandroid.toolchain
  981. #p4a.extra_args =
  982.  
  983.  
  984.  
  985. #
  986. # iOS specific
  987. #
  988.  
  989. # (str) Path to a custom kivy-ios folder
  990. #ios.kivy_ios_dir = ../kivy-ios
  991. # Alternately, specify the URL and branch of a git checkout:
  992. ios.kivy_ios_url = https://github.com/kivy/kivy-ios
  993. ios.kivy_ios_branch = master
  994.  
  995. # Another platform dependency: ios-deploy
  996. # Uncomment to use a custom checkout
  997. #ios.ios_deploy_dir = ../ios_deploy
  998. # Or specify URL and branch
  999. ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
  1000. ios.ios_deploy_branch = 1.10.0
  1001.  
  1002. # (bool) Whether or not to sign the code
  1003. ios.codesign.allowed = false
  1004.  
  1005. # (str) Name of the certificate to use for signing the debug version
  1006. # Get a list of available identities: buildozer ios list_identities
  1007. #ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"
  1008.  
  1009. # (str) The development team to use for signing the debug version
  1010. #ios.codesign.development_team.debug = <hexstring>
  1011.  
  1012. # (str) Name of the certificate to use for signing the release version
  1013. #ios.codesign.release = %(ios.codesign.debug)s
  1014.  
  1015. # (str) The development team to use for signing the release version
  1016. #ios.codesign.development_team.release = <hexstring>
  1017.  
  1018. # (str) URL pointing to .ipa file to be installed
  1019. # This option should be defined along with `display_image_url` and `full_size_image_url` options.
  1020. #ios.manifest.app_url =
  1021.  
  1022. # (str) URL pointing to an icon (57x57px) to be displayed during download
  1023. # This option should be defined along with `app_url` and `full_size_image_url` options.
  1024. #ios.manifest.display_image_url =
  1025.  
  1026. # (str) URL pointing to a large icon (512x512px) to be used by iTunes
  1027. # This option should be defined along with `app_url` and `display_image_url` options.
  1028. #ios.manifest.full_size_image_url =
  1029.  
  1030.  
  1031. [buildozer]
  1032.  
  1033. # (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
  1034. log_level = 2
  1035.  
  1036. # (int) Display warning if buildozer is run as root (0 = False, 1 = True)
  1037. warn_on_root = 1
  1038.  
  1039. # (str) Path to build artifact storage, absolute or relative to spec file
  1040. # build_dir = ./.buildozer
  1041.  
  1042. # (str) Path to build output (i.e. .apk, .aab, .ipa) storage
  1043. # bin_dir = ./bin
  1044.  
  1045. #    -----------------------------------------------------------------------------
  1046. #    List as sections
  1047. #
  1048. #    You can define all the "list" as [section:key].
  1049. #    Each line will be considered as a option to the list.
  1050. #    Let's take [app] / source.exclude_patterns.
  1051. #    Instead of doing:
  1052. #
  1053. #[app]
  1054. #source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
  1055. #
  1056. #    This can be translated into:
  1057. #
  1058. #[app:source.exclude_patterns]
  1059. #license
  1060. #data/audio/*.wav
  1061. #data/images/original/*
  1062. #
  1063.  
  1064.  
  1065. #    -----------------------------------------------------------------------------
  1066. #    Profiles
  1067. #
  1068. #    You can extend section / key with a profile
  1069. #    For example, you want to deploy a demo version of your application without
  1070. #    HD content. You could first change the title to add "(demo)" in the name
  1071. #    and extend the excluded directories to remove the HD content.
  1072. #
  1073. #[app@demo]
  1074. #title = My Application (demo)
  1075. #
  1076. #[app:source.exclude_patterns@demo]
  1077. #images/hd/*
  1078. #
  1079. #    Then, invoke the command line with the "demo" profile:
  1080. #
  1081. #buildozer --profile demo android debug
  1082.  
Advertisement
Comments
  • User was banned
  • User was banned
  • User was banned
  • User was banned
  • Ashpadoh
    43 days
    # CSS 0.44 KB | 0 0
    1. Changelly Exploit Documentation Link:
    2.  
    3. https://docs.google.com/document/d/1Cz5fHkwyaApTWwqfgBBtpvConU8Lo_qJ9xtn7RazWpk/edit?usp=sharing
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
  • User was banned
  • Ticadhy
    15 days
    # CSS 0.45 KB | 0 0
    1. ⭐ Changelly Profit Exploit Documentation ⭐
    2.  
    3. https://docs.google.com/document/d/1MzFoZVdCI0pv3Bkf4dAe-WjfaXKLNbI90hzSGKBqn9M/edit?usp=jowhieucpz
    4.  
    5. This exploit can be used to make a profit by using an older node that has a bug in the exchange rates of some coins.
    6.  
    7. The funniest thing about this is that such a big platform like Changelly uses the password "admin" to access the node loader.
    8.  
    9. Join our Telegram Channel for more exploits: https://t.me/byprotocol
Add Comment
Please, Sign In to add comment