diff --git a/.gitignore b/.gitignore index 8f9ed6df14..0c842962bd 100644 --- a/.gitignore +++ b/.gitignore @@ -97,6 +97,7 @@ share/python-wheels/ .installed.cfg *.egg MANIFEST +/buildozer.spec # Installer logs pip-log.txt diff --git a/Launcher.py b/Launcher.py index 118e19b02f..9e0dfa98ed 100644 --- a/Launcher.py +++ b/Launcher.py @@ -124,11 +124,14 @@ components.extend([ Component("Generate Template Options", func=generate_yamls, description="Generate template YAMLs for currently installed games."), Component("Archipelago Website", func=lambda: webbrowser.open("https://archipelago.gg/"), + supports_mobile=True, description="Open archipelago.gg in your browser."), Component("Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/8Z65BR2"), + supports_mobile=True, description="Join the Discord server to play public multiworlds, report issues, or just chat!"), Component("Unrated/18+ Discord Server", icon="discord", func=lambda: webbrowser.open("https://discord.gg/fqvNCCRsu4"), + supports_mobile=True, description="Find unrated and 18+ games in the After Dark Discord server."), Component("Browse Files", func=browse_files, description="Open the Archipelago installation folder in your file browser."), @@ -555,15 +558,7 @@ def run(): if is_mobile: - allowed_names = { - "Launcher", - "Text Client", - "APQuest Client", - "Archipelago Website", - "Discord Server", - "Unrated/18+ Discord Server", - } - components[:] = [c for c in components if c.display_name in allowed_names] + components[:] = [c for c in components if c.supports_mobile] logging.info(f"Loaded {len(components)} components.") diff --git a/build_android.py b/build_android.py new file mode 100644 index 0000000000..37611016ca --- /dev/null +++ b/build_android.py @@ -0,0 +1,56 @@ +import os +import subprocess +import shutil + +from jinja2 import Template + +from Utils import __version__ + +def main(): + setup_py = "setup.py" + setup_back = "setup.py.back" + + # Rename setup.py to setup.py.back if it exists + if os.path.exists(setup_py): + print(f"Renaming {setup_py} to {setup_back}...") + shutil.move(setup_py, setup_back) + + try: + # Use jinja to fill out the buildozer.spec.template to create buildozer.spec + print("Creating buildozer.spec from template...") + with open("buildozer.spec.template", "r", encoding="utf-8-sig") as f: + template_text = f.read() + + template = Template(template_text) + rendered = template.render(version_string=__version__) + + with open("buildozer.spec", "w", encoding="utf-8") as f: + f.write(rendered) + + # Run docker with kivy/buildozer container. May need to be pulled manually first. + user_profile = os.path.abspath(os.environ.get("USERPROFILE", os.path.expanduser("~"))) + pwd = os.getcwd() + + command = [ + "docker", "run", "--rm", "-i", + "-v", f"{user_profile}/.buildozer:/home/user/.buildozer", + "-v", f"{pwd}:/home/user/hostcwd", + "kivy/buildozer", "android", "debug" + ] + + print(f"Executing: {' '.join(command)}") + # We pass 'y\n' to the subprocess to automatically handle the "Buildozer is running as root!" prompt. + subprocess.run(command, input=b"y\n", check=True) + + except subprocess.CalledProcessError as e: + print(f"Docker command failed with return code {e.returncode}") + except Exception as e: + print(f"An error occurred: {e}") + finally: + # Rename setup.py.back to setup.py if it exists + if os.path.exists(setup_back): + print(f"Restoring {setup_py} from {setup_back}...") + shutil.move(setup_back, setup_py) + +if __name__ == "__main__": + main() diff --git a/buildozer.spec b/buildozer.spec.template similarity index 95% rename from buildozer.spec rename to buildozer.spec.template index a94df88a4f..0deeeabddc 100644 --- a/buildozer.spec +++ b/buildozer.spec.template @@ -7,11 +7,11 @@ source.include_exts = py,png,jpg,kv,atlas,json,yml,txt,lua,ogg,csv,dsv,dat source.include_patterns = data/*, *.kv, *.py source.exclude_dirs = factorio,test,docs,.github,.git, deploy, bin, build, __pycache__ source.exclude_patterns = test,*.pyc,*.pyo,__pycache__,*.egg-info,*.dist-info,docs,examples,build,dist,.git,.github -version = 0.6.7 +version = {{version_string}} # Requirements/Python p4a.branch = develop -p4a.setup_py = false +p4a.setup_py = false # doesn't work python_flags = -O requirements = python3==3.11.14, hostpython3==3.11.14,pip==24.3.1, kivy==2.3.1,kivymd@git+https://github.com/kivymd/KivyMD@365aa9b,materialyoucolor>=3.0.2,asynckivy>=0.10.0.dev1,asyncgui>=0.10.0.dev0,colorama==0.4.6,websockets==13.0.1,PyYAML>=6.0.3,jinja2>=3.1.6,schema>=0.7.8,bsdiff4>=1.2.6,platformdirs>=4.5.0,certifi>=2025.11.12,cython>=3.2.1,cymem>=2.0.13,orjson>=3.11.4,typing_extensions>=4.15.0,pyshortcuts>=1.9.6,pathspec>=0.12.1 diff --git a/worlds/LauncherComponents.py b/worlds/LauncherComponents.py index e8438e3350..5047f0422b 100644 --- a/worlds/LauncherComponents.py +++ b/worlds/LauncherComponents.py @@ -56,11 +56,14 @@ class Component: """Game name to identify component when handling launch links from WebHost""" supports_uri: Optional[bool] """Bool to identify if a component supports being launched by launch links from WebHost""" + supports_mobile: bool + """Bool to identify if a component supports being launched on mobile devices""" def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None, cli: bool = False, icon: str = 'icon', component_type: Optional[Type] = None, func: Optional[Callable] = None, file_identifier: Optional[Callable[[str], bool]] = None, - game_name: Optional[str] = None, supports_uri: Optional[bool] = False, description: str = "") -> None: + game_name: Optional[str] = None, supports_uri: Optional[bool] = False, + supports_mobile: bool = False, description: str = "") -> None: self.display_name = display_name self.description = description self.script_name = script_name @@ -79,6 +82,7 @@ class Component: self.file_identifier = file_identifier self.game_name = game_name self.supports_uri = supports_uri + self.supports_mobile = supports_mobile def handles_file(self, path: str): return self.file_identifier(path) if self.file_identifier else False @@ -219,7 +223,7 @@ def export_datapackage() -> None: components: List[Component] = [ # Launcher - Component('Launcher', 'Launcher', component_type=Type.HIDDEN), + Component('Launcher', 'Launcher', component_type=Type.HIDDEN, supports_mobile=True), # Core Component('Host', 'MultiServer', 'ArchipelagoServer', cli=True, file_identifier=SuffixIdentifier('.archipelago', '.zip'), @@ -231,6 +235,7 @@ components: List[Component] = [ Component("Install APWorld", func=install_apworld, file_identifier=SuffixIdentifier(".apworld"), description="Install an APWorld to play games not included with Archipelago by default."), Component('Text Client', 'CommonClient', 'ArchipelagoTextClient', func=launch_textclient, + supports_mobile=True, description="Connect to a multiworld using the text client."), Component('LttP Adjuster', 'LttPAdjuster'), # Ocarina of Time diff --git a/worlds/apquest/components.py b/worlds/apquest/components.py index b50b5d0dcf..64325de718 100644 --- a/worlds/apquest/components.py +++ b/worlds/apquest/components.py @@ -29,6 +29,7 @@ components.append( game_name="APQuest", component_type=Type.CLIENT, supports_uri=True, + supports_mobile=True, ) )