diff --git a/sbapp/main.py b/sbapp/main.py index 358039a..1217123 100644 --- a/sbapp/main.py +++ b/sbapp/main.py @@ -15,6 +15,7 @@ import RNS import LXMF import time import os +import pathlib import plyer import base64 import threading @@ -149,7 +150,11 @@ class SidebandApp(MDApp): self.conversations_view = None self.messages_view = None + self.map = None + self.map_layer = None self.map_screen = None + self.offline_source = None + self.map_settings_screen = None self.object_details_screen = None self.sync_dialog = None self.settings_ready = False @@ -3278,71 +3283,155 @@ class SidebandApp(MDApp): def map_fm_got_path(self, path): self.map_fm_exited() - # TODO: Remove - toast(path) - ### try: source = MBTilesMapSource(path) - self.map_update_source(source) + self.map_update_source() self.sideband.config["map_storage_file"] = path + self.sideband.config["map_storage_path"] = str(pathlib.Path(path).parent.resolve()) self.sideband.save_configuration() + toast("Using \""+os.path.basename(path)+"\" as offline map") except Exception as e: RNS.log(f"Error while loading map \"{path}\": "+str(e), RNS.LOG_ERROR) - selfmap_update_source() + toast("Could not load map \""+os.path.basename(path)+"\"") + self.sideband.config["map_storage_file"] = None + self.sideband.config["map_use_offline"] = False + self.sideband.config["map_use_online"] = True + self.map_settings_load_states() + self.map_update_source() def map_fm_exited(self, *args): - RNS.log("Closing file manager", RNS.LOG_DEBUG) - self.sideband.config["map_storage_file"] = None - self.sideband.save_configuration() self.manager_open = False self.file_manager.close() self.map_update_source() - def map_get_source(self): - current_map_path = self.sideband.config["map_storage_file"] - source = None - - if current_map_path: + def map_get_offline_source(self): + if self.offline_source != None: + return self.offline_source + else: try: + current_map_path = self.sideband.config["map_storage_file"] + if current_map_path == None: + raise ValueError("Map path cannot be None") source = MBTilesMapSource(current_map_path) + self.offline_source = source + return self.offline_source + except Exception as e: RNS.log(f"Error while loading map from \"{current_map_path}\": "+str(e)) self.sideband.config["map_storage_file"] = None + self.sideband.config["map_use_offline"] = False + self.sideband.config["map_use_online"] = True self.sideband.save_configuration() + self.map_settings_load_states() + + return None + + def map_get_source(self): + source = None + if self.sideband.config["map_use_offline"]: + source = self.map_get_offline_source() if source == None: - source = MapSource.from_provider("osm") + source = MapSource.from_provider("osm", quad_key=False) return source def map_update_source(self, source=None): ns = source or self.map_get_source() if self.map != None: + + if source != None: + maxz = source.max_zoom + minz = source.min_zoom + if self.map.zoom > maxz: + self.map.zoom = maxz + + if self.map.zoom < minz: + self.map.zoom = minz + + m = self.map + RNS.log(f"Map {m.lat} {m.lon} {m.zoom}") + nlat = self.map.lat + nlon = self.map.lon + if nlat < -89: nlat = -89 + if nlat > 89: nlat = 89 + if nlon < -179: nlon = -179 + if nlon > 179: nlon = 179 + self.map.center_on(nlat,nlon) + + self.map.map_source = ns - def map_test_action(self, sender=None): + def map_layers_action(self, sender=None): + try: + ml = self.map_layer + layers = [] + if self.sideband.config["map_use_offline"]: + layers.append("offline") + + if self.sideband.config["map_use_online"]: + layers.append("osm") + layers.append("ve") + + if ml == None: ml = layers[0] + + if not ml in layers: + ml = layers[0] + + mli = layers.index(ml) + mli = (mli+1)%len(layers) + ml = layers[mli] + + source = None + if ml == "offline": source = self.map_get_offline_source() + if ml == "osm": source = MapSource.from_provider("osm", quad_key=False) + if ml == "ve": source = MapSource.from_provider("ve", quad_key=True) + + if source != None: + self.map_layer = ml + self.map_update_source(source) + except Exception as e: + RNS.log("Error while switching map layer: "+str(e), RNS.LOG_ERROR) + + def map_select_file_action(self, sender=None): perm_ok = False - if RNS.vendor.platformutils.is_android(): - perm_ok = self.check_storage_permission() + if self.sideband.config["map_storage_path"] == None: + if RNS.vendor.platformutils.is_android(): + perm_ok = self.check_storage_permission() - if self.sideband.config["map_storage_external"]: - path = secondary_external_storage_path() - if path == None: path = primary_external_storage_path() - else: - path = primary_external_storage_path() + if self.sideband.config["map_storage_external"]: + path = secondary_external_storage_path() + if path == None: path = primary_external_storage_path() + else: + path = primary_external_storage_path() + else: + perm_ok = True + if self.sideband.config["map_storage_external"]: + path = "/" + else: + path = os.path.expanduser("~") else: perm_ok = True - path = os.path.expanduser("~") + path = self.sideband.config["map_storage_path"] if perm_ok and path != None: - self.file_manager = MDFileManager( - exit_manager=self.map_fm_exited, - select_path=self.map_fm_got_path, - ) - self.file_manager.ext = [".mbtiles"] - self.file_manager.show(path) + try: + self.file_manager = MDFileManager( + exit_manager=self.map_fm_exited, + select_path=self.map_fm_got_path, + ) + self.file_manager.ext = [".mbtiles"] + self.file_manager.show(path) + + except Exception as e: + self.sideband.config["map_storage_path"] = None + self.sideband.save_configuration() + toast("Error reading directory, check permissions!") + else: + self.sideband.config["map_storage_path"] = None + self.sideband.save_configuration() toast("No file access, check permissions!") def map_action(self, sender=None, direction="left"): @@ -3371,6 +3460,60 @@ class SidebandApp(MDApp): self.map_update_markers() Clock.schedule_once(am_job, 0.6) + def map_settings_load_states(self): + if self.map_settings_screen != None: + self.map_settings_screen.ids.map_use_online.active = self.sideband.config["map_use_online"] + self.map_settings_screen.ids.map_use_offline.active = self.sideband.config["map_use_offline"] + self.map_settings_screen.ids.map_storage_external.active = self.sideband.config["map_storage_external"] + + def map_settings_init(self): + self.map_settings_load_states() + def map_settings_save(sender=None, event=None): + self.sideband.config["map_storage_external"] = self.map_settings_screen.ids.map_storage_external.active + self.sideband.config["map_use_online"] = self.map_settings_screen.ids.map_use_online.active + self.sideband.config["map_use_offline"] = self.map_settings_screen.ids.map_use_offline.active + self.sideband.save_configuration() + + def external_toggle(sender=None, event=None): + self.sideband.config["map_storage_path"] = None + map_settings_save() + + def offline_toggle(sender=None, event=None): + if self.map_settings_screen.ids.map_use_offline.active: + # self.map_settings_screen.ids.map_use_online.active = False + if self.sideband.config["map_storage_file"] == None: + self.map_select_file_action() + else: + self.map_settings_screen.ids.map_use_online.active = True + map_settings_save(); self.map_update_source() + + def online_toggle(sender=None, event=None): + if self.map_settings_screen.ids.map_use_online.active: + # self.map_settings_screen.ids.map_use_offline.active = False + pass + else: + self.map_settings_screen.ids.map_use_offline.active = True + map_settings_save(); self.map_update_source() + + + self.map_settings_screen.ids.map_use_offline.bind(active=offline_toggle) + self.map_settings_screen.ids.map_use_online.bind(active=online_toggle) + self.map_settings_screen.ids.map_storage_external.bind(active=external_toggle) + + def map_settings_action(self, sender=None, direction="left"): + if not self.root.ids.screen_manager.has_screen("map_settings_screen"): + self.map_settings_screen = Builder.load_string(layout_map_settings_screen) + self.map_settings_screen.app = self + self.root.ids.screen_manager.add_widget(self.map_settings_screen) + self.map_settings_screen.ids.map_config_info.text = "\n\nSideband can use map sources from the Internet, or a map source stored locally on this device in MBTiles format." + self.map_settings_screen.ids.map_settings_scrollview.effect_cls = ScrollEffect + self.map_settings_init() + + self.root.ids.screen_manager.transition.direction = direction + self.root.ids.screen_manager.current = "map_settings_screen" + self.root.ids.nav_drawer.set_state("closed") + self.sideband.setstate("app.displaying", self.root.ids.screen_manager.current) + def close_location_error_dialog(self, sender=None): if hasattr(self, "location_error_dialog") and self.location_error_dialog != None: self.location_error_dialog.dismiss() @@ -3391,7 +3534,7 @@ class SidebandApp(MDApp): if not location: self.location_error_dialog = MDDialog( title="No Location", - text="During the last 24 hours, no location updates have been received from this peer. You can use the the [b]Situation Map[/b] to manually search for earlier telemetry.", + text="No location updates have been received from this peer. You can use the the [b]Situation Map[/b] to manually search for earlier telemetry.", buttons=[ MDRectangleFlatButton( text="OK", diff --git a/sbapp/sideband/core.py b/sbapp/sideband/core.py index b8b3a2b..d3ba182 100644 --- a/sbapp/sideband/core.py +++ b/sbapp/sideband/core.py @@ -171,8 +171,13 @@ class SidebandCore(): try: if not os.path.isfile(self.config_path): self.__init_config() - else: self.__load_config() + else: + try: + self.__load_config() + except Exception as e: + self.__init_config() + self.__load_config() self.first_run = False if self.config["debug"]: @@ -533,6 +538,13 @@ class SidebandCore(): self.config["map_zoom"] = 3 if not "map_storage_external" in self.config: self.config["map_storage_external"] = False + if not "map_use_offline" in self.config: + self.config["map_use_offline"] = False + if not "map_use_online" in self.config: + self.config["map_use_online"] = True + if not "map_layer" in self.config: + self.config["map_layer"] = None + if not "map_storage_path" in self.config: self.config["map_storage_path"] = None if not "map_storage_file" in self.config: @@ -814,7 +826,7 @@ class SidebandCore(): return [] def peer_location(self, context_dest): - after_time = time.time()-24*60*60 + after_time = time.time()-3*30*24*60*60 pts = self.peer_telemetry(context_dest, after=after_time) for pt in pts: try: diff --git a/sbapp/ui/layouts.py b/sbapp/ui/layouts.py index f854ee5..eac34eb 100644 --- a/sbapp/ui/layouts.py +++ b/sbapp/ui/layouts.py @@ -851,6 +851,117 @@ MDScreen: height: dp(256) """ +layout_map_settings_screen = """ +MDScreen: + name: "map_settings_screen" + + BoxLayout: + orientation: "vertical" + + MDTopAppBar: + title: "Map Configuration" + anchor_title: "left" + elevation: 0 + left_action_items: + [['menu', lambda x: root.app.nav_drawer.set_state("open")]] + right_action_items: + [ + ['close', lambda x: root.app.close_sub_map_action(self)], + ] + + ScrollView: + id: map_settings_scrollview + + MDBoxLayout: + orientation: "vertical" + size_hint_y: None + height: self.minimum_height + padding: [dp(28), dp(48), dp(28), dp(16)] + + MDLabel: + text: "Configure Map" + font_style: "H6" + + MDLabel: + id: map_config_info + markup: True + text: "" + size_hint_y: None + text_size: self.width, None + height: self.texture_size[1] + + MDLabel: + markup: True + text: "\\n" + size_hint_y: None + text_size: self.width, None + height: self.texture_size[1] + + MDBoxLayout: + orientation: "horizontal" + padding: [0,0,dp(24),0] + size_hint_y: None + height: dp(48) + + MDLabel: + text: "Use online map sources" + font_style: "H6" + + MDSwitch: + id: map_use_online + pos_hint: {"center_y": 0.3} + active: False + + MDBoxLayout: + orientation: "horizontal" + padding: [0,0,dp(24),0] + size_hint_y: None + height: dp(48) + + MDLabel: + text: "Use offline map source" + font_style: "H6" + + MDSwitch: + id: map_use_offline + pos_hint: {"center_y": 0.3} + active: False + + MDBoxLayout: + orientation: "horizontal" + padding: [0,0,dp(24),0] + size_hint_y: None + height: dp(48) + + MDLabel: + id: map_storage_external_label + text: "Use external storage path" + font_style: "H6" + + MDSwitch: + id: map_storage_external + pos_hint: {"center_y": 0.3} + active: False + + MDBoxLayout: + orientation: "vertical" + size_hint_y: None + height: self.minimum_height + padding: [0, dp(24), 0, 0] + + MDRectangleFlatIconButton: + id: telemetry_icons_button + icon: "list-box-outline" + text: "Select MBTiles Map" + padding: [dp(0), dp(14), dp(0), dp(14)] + icon_size: dp(24) + font_size: dp(16) + size_hint: [1.0, None] + on_release: root.app.map_select_file_action(self) + disabled: False +""" + + layout_map_screen = """ MDScreen: name: "map_screen" @@ -869,7 +980,8 @@ MDScreen: ['format-list-bulleted-type', lambda x: root.app.map_object_list(self)], ['arrow-down-bold-hexagon-outline', lambda x: root.app.telemetry_request_action(self)], ['upload-lock', lambda x: root.app.telemetry_send_update(self)], - ['wrench-cog', lambda x: root.app.map_test_action(self)], + ['layers', lambda x: root.app.map_layers_action(self)], + ['wrench-cog', lambda x: root.app.map_settings_action(self)], ['close', lambda x: root.app.close_any_action(self)], ]