Разработка приложения, использующего карты, или любого другого многоэкранного приложения

Структура проекта

name_app/

├── main.py          # Ваш код приложения

├── flet.toml          # файл настройки/информации о приложении

├── icon.png          # иконка приложения

└── assets/          # (Опционально) Папка с картинками, иконками и HTML-картой

Обязательно установить библиотеку flet через меню установки библиотек


Пример файла main.py

import ssl

import flet as ft
import os
import urllib.request
import json

# Переменная для хранения данных. Теперь мы оставляем её пустой по умолчанию,
# так как данные будут загружаться динамически из Django или оффлайн-файла.
ROUTES_DATA = []


# ФУНКЦИЯ ДЛЯ СКАЧИВАНИЯ ДАННЫХ И ОФФЛАЙН-КЭШИРОВАНИЯ
def load_data_from_server():
global ROUTES_DATA
# Используем ваш работающий URL (с буквой s, так как SSL на Timeweb активен!)
url = "https://media.infostarting.ru/api/routes/"
cache_file = "offline_data.json"

# Создаем на устройстве папку для оффлайн-картинок
local_images_dir = "cached_images"
if not os.path.exists(local_images_dir):
os.makedirs(local_images_dir)

try:
print("Синхронизация с сервером Django...")
context = ssl._create_unverified_context()
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})

with urllib.request.urlopen(req, timeout=5) as response:
html = response.read().decode('utf-8')
server_data = json.loads(html)

# --- КЭШИРОВАНИЕ КАРТИНОК В ПАМЯТЬ ТЕЛЕФОНА ---
for route in server_data:
# 1. Скачиваем главную картинку маршрута
if route.get("image") and route["image"].startswith("http"):
img_url = route["image"]
img_name = img_url.split("/")[-1]
local_path = os.path.join(local_images_dir, img_name)
try:
urllib.request.urlretrieve(img_url, local_path)
route["image"] = local_path # Подменяем ссылку на локальный путь
except Exception as e:
print(f"Ошибка кэширования картинки маршрута: {e}")

# 2. Скачиваем картинки для каждой точки
for pt in route.get("points", []):
if pt.get("image") and pt["image"].startswith("http"):
img_url = pt["image"]
img_name = img_url.split("/")[-1]
local_path = os.path.join(local_images_dir, img_name)
try:
urllib.request.urlretrieve(img_url, local_path)
pt["image"] = local_path # Подменяем ссылку на локальный путь
except Exception as e:
print(f"Ошибка кэширования картинки точки: {e}")

# Сохраняем обновленный JSON со всеми локальными путями
ROUTES_DATA = server_data
with open(cache_file, "w", encoding="utf-8") as f:
json.dump(ROUTES_DATA, f, ensure_ascii=False, indent=4)
print("Синхронизация завершена! Картинки и тексты сохранены оффлайн.")

except Exception as e:
print(f"Интернета нет или сервер недоступен ({e}). Включаем оффлайн-режим...")
try:
with open(cache_file, "r", encoding="utf-8") as f:
ROUTES_DATA = json.load(f)
print("Автономный путеводитель успешно загружен из памяти устройства!")
except FileNotFoundError:
print("Локальный кэш отсутствует.")
ROUTES_DATA = []


# ЦВЕТА ИСТОРИЧЕСКОГО СТИЛЯ
COLOR_BG = "#FDFBF7"
COLOR_PRIMARY = "#5C3A21"
COLOR_ACCENT = "#C87A24"
COLOR_CARD = "#F5EFEB"


def main(page: ft.Page):
page.title = "Галич Путеводитель"
page.theme_mode = ft.ThemeMode.LIGHT
page.bgcolor = COLOR_BG
page.window.width = 390
page.window.height = 844

# --- ЭКРАН 3: ПОДРОБНОЕ ОПИСАНИЕ ТОЧКИ ---
def show_point_details(route, pt_item):
page.clean()

page.add(
ft.AppBar(
title=ft.Text(pt_item["title"], size=16, color=ft.Colors.WHITE),
bgcolor=COLOR_PRIMARY,
leading=ft.IconButton(ft.Icons.ARROW_BACK, icon_color=ft.Colors.WHITE,
on_click=lambda e: show_route_points(route))
),
ft.ListView(
controls=[
ft.Image(src=pt_item["image"], fit=ft.BoxFit.COVER, height=220),
ft.Container(
padding=16,
content=ft.Column([
ft.Text(pt_item["title"], size=22, weight=ft.FontWeight.BOLD, color=COLOR_PRIMARY),
ft.Button(
"Показать на карте",
icon=ft.Icons.MAP,
color=ft.Colors.WHITE,
bgcolor=COLOR_ACCENT,
# Напрямую привязываем системное действие открытия ссылки
action=ft.OpenUrl(f"https://yandex.ru/maps/?rtext=~{pt_item['coordinates']}")
),
ft.Divider(),
ft.Text(pt_item["full_desc"], size=15, color="#333333")
], spacing=12)
)
],
expand=True
)
)

# --- ЭКРАН 2: ТОЧКИ ВЫБРАННОГО МАРШРУТА ---
def show_route_points(route):
page.clean()
import urllib.parse

# 1. Собираем чистые координаты для фиолетовой линии маршрута
raw_coords = [pt["coordinates"] for pt in route["points"] if pt.get("coordinates")]
# Склеиваем их через тильду (например, "58.3792,42.3486~58.3810,42.3500")
route_line_string = "~".join(raw_coords)

# 2. Создаем перечень красивых меток с названиями из Django
# Формат Яндекса для меток: Долгота,Широта,тип_метки(pm2rdm)~...
# ВНИМАНИЕ: Для меток Яндекс требует сначала Долготу, потом Широту! Меняем их местами.
label_points = []
for index, pt in enumerate(route["points"]):
if pt.get("coordinates") and pt.get("title"):
# Разделяем координаты на широту и долготу
lat, lon = pt["coordinates"].split(",")
safe_title = urllib.parse.quote(pt["title"])
# Задаем стиль метки (pm2rdm - красный кружок с номером точки)
label_points.append(f"{lon},{lat},pm2rdm{index + 1}~{safe_title}")

# Склеиваем метки через знак плюс для Яндекса
labels_string = "+".join(label_points)

points_tiles = []
for pt_item in route["points"]:
points_tiles.append(
ft.Container(
bgcolor=COLOR_CARD,
border_radius=8,
padding=8,
on_click=lambda e, pt=pt_item: show_point_details(route, pt),
content=ft.Row([
ft.Image(src=pt_item["image"], width=80, height=80, fit=ft.BoxFit.COVER, border_radius=6),
ft.Column([
ft.Text(pt_item["title"], size=16, weight=ft.FontWeight.BOLD, color=COLOR_PRIMARY),
ft.Text(pt_item["short_desc"], size=13, color="#666666", width=220, max_lines=2,
overflow=ft.TextOverflow.ELLIPSIS)
], spacing=4)
])
)
)

page.add(
ft.AppBar(
title=ft.Text(route["title"], size=16, color=ft.Colors.WHITE),
bgcolor=COLOR_PRIMARY,
leading=ft.IconButton(ft.Icons.ARROW_BACK, icon_color=ft.Colors.WHITE,
on_click=lambda e: show_routes_list())
),
ft.Container(
padding=16,
content=ft.ListView(
controls=[
ft.Text("Пункты маршрута:", size=18, weight=ft.FontWeight.BOLD, color=COLOR_PRIMARY),
ft.Divider(),
# 3. Наша новая крутая кнопка для прокладывания всего пешеходного пути!
ft.Button(
"Построить маршрут в Яндекс.Картах",
icon=ft.Icons.DIRECTIONS_WALK, # Красивая иконка пешехода
color=ft.Colors.WHITE,
bgcolor=COLOR_ACCENT,
# Передаем готовую ссылку Яндексу. rtt=pd включит пешеходный режим
url=f"https://yandex.ru/maps/?rtext={route_line_string}&rtt=pd&pt={labels_string}&z=auto"
),
ft.Divider(), # Маленький разделитель под кнопкой для красоты

*points_tiles
],
spacing=12,
expand=True
)
)
)

# --- ЭКРАН 1: ГЛАВНЫЙ СПИСОК МАРШРУТОВ ---
def show_routes_list():
page.clean()

# Если сервер выключен И кэша нет (первый запуск), выводим понятное предупреждение
if not ROUTES_DATA:
page.add(
ft.AppBar(title=ft.Text("Галичский Путеводитель", color=ft.Colors.WHITE), bgcolor=COLOR_PRIMARY),
ft.Container(
padding=20,
content=ft.Text("Не удалось загрузить данные. Проверьте, запущен ли сервер Django на компьютере.",
size=16, color="red")
)
)
return

routes_cards = []
for route in ROUTES_DATA:
routes_cards.append(
ft.Card(
bgcolor=COLOR_CARD,
elevation=3,
content=ft.Container(
padding=12,
content=ft.Column([
ft.Image(src=route["image"], border_radius=8, fit=ft.BoxFit.COVER, height=150, width=400),
ft.Text(route["title"], size=18, weight=ft.FontWeight.BOLD, color=COLOR_PRIMARY),
ft.Text(route["description"], size=14, color="#555555", max_lines=3,
overflow=ft.TextOverflow.ELLIPSIS),
ft.Row([
ft.Button(
"Выбрать маршрут",
color=ft.Colors.WHITE,
bgcolor=COLOR_ACCENT,
on_click=lambda e, r=route: show_route_points(r)
)
], alignment=ft.MainAxisAlignment.END)
], spacing=8)
)
)
)

page.add(
ft.AppBar(
title=ft.Text("Галичский Путеводитель", color=ft.Colors.WHITE, weight=ft.FontWeight.BOLD),
bgcolor=COLOR_PRIMARY,
automatically_imply_leading=False
),
ft.Container(
padding=16,
content=ft.ListView(controls=routes_cards, spacing=16, expand=True)
)
)

# Сначала запускаем скачивание из Django, а затем открываем меню
load_data_from_server()
show_routes_list()


if __name__ == "__main__":
ft.run(
main,
#host="0.0.0.0",
#port=8550,
#view=ft.AppView.WEB_BROWSER, #откроет в браузере
#view=ft.AppView.FLET_APP,
assets_dir="assets"
)

Пример файла flet.toml

[app]
name = "Галич Путеводитель"
description = "Интерактивный путеводитель по городу Галичу"
# Уникальный технический идентификатор приложения
id = "ru.media.infostarting.galich.guide"
author = "Ярослав Злобин"
version = "1.0.0"
build_number = 1
icon = "icon.png"
[android]
# Разрешаем приложению выходить в мобильный интернет для скачивания картинок из Django
permissions = ["android.permission.INTERNET"]
[android.queries]
intents = [
{ action = "android.intent.action.VIEW", scheme = "https" },
{ action = "android.intent.action.VIEW", scheme = "http" }
]

Команда терминала для запуска и тестирования в приложении flet


flet run --android main.py


Последнее изменение: Воскресенье, 27 сентября 2026, 12:14