# From KivyMD which will remove it in their next version, with some fixes from kivy.animation import Animation from kivy.clock import Clock from kivy.lang import Builder from kivy.network.urlrequest import UrlRequest from kivy.properties import ObjectProperty, StringProperty from kivy.uix.boxlayout import BoxLayout Builder.load_string( """ #:import Window kivy.core.window.Window opacity: 0 spacing: 10 size_hint_y: None height: dp(25) MDSpinner id: spinner size_hint: None, 0.8 size: dp(23), dp(23) color: 0.95,0.95,0.95,1 MDLabel: id: label_download max_lines: 2 shorten: True shorten_from: 'right' halign: 'left' valign: 'center' text_size: self.size height: dp(23) color: 0.95,0.95,0.95,1 text: root.label_downloading_text """ ) class ProgressLoader(BoxLayout): path_to_file = StringProperty() """The path to which the uploaded file will be saved.""" download_url = StringProperty() """Link to uploaded file.""" label_downloading_text = StringProperty("Downloading...") """Default text before downloading.""" downloading_text = StringProperty("Downloading: {}%") """Progress text of the downloaded file.""" download_complete = ObjectProperty() """Function, called after a successful file upload.""" download_error = ObjectProperty() """Function, called after an error in downloading.""" download_redirected = ObjectProperty() """Function, called after a redirect event.""" request = ObjectProperty() """UrlRequest object.""" def __init__(self, **kwargs): super().__init__(**kwargs) self.root_instance = None self.request = None def start(self, root_instance): self.root_instance = root_instance self.root_instance.add_widget(self) self.request_download_file(self.download_url, self.path_to_file) Clock.schedule_once(self.animation_show, 1) def animation_show(self, _dt): animation = Animation(opacity=1, d=0.2, t="out_quad") animation.start(self) def request_download_file(self, url, path): """ :type url: str; :param url: link to content; :type path: str; :param path: path to save content; """ self.request = UrlRequest( url, file_path=path, chunk_size=102400, on_progress=self.update_progress, on_success=self.handle_success, on_redirect=self.handle_redirect, on_error=lambda req, error: self.handle_error(req, error), on_failure=lambda req, res: self.handle_error(req, "Failure"), on_cancel=lambda req: self.handle_error(req, "Cancelled"), ) def handle_redirect(self, request, *_args): new_url = request.resp_headers.get("location") or request.resp_headers.get("Location") if new_url: self.download_url = new_url self.request_download_file(self.download_url, self.path_to_file) else: self.handle_error() if self.download_redirected: self.download_redirected(request) def cleanup(self): self.root_instance.remove_widget(self) def handle_error(self, request, error): status = f"Error: {error}" if request.resp_status: status += f" ({request.resp_status})" self.label_downloading_text = self.downloading_text.format(status) self.ids.spinner.active = False if self.download_error: self.download_error(request, error) def update_progress(self, request, current_size, total_size): if total_size < 1e4: current_size = 0 percent = current_size / max(total_size, 1) self.label_downloading_text = self.downloading_text.format(f"{percent:.1%}") def handle_success(self, request, result): self.cleanup() if self.download_complete: self.download_complete(request)