def __init__(self, **kwargs): super(ChatBox, self).__init__(**kwargs) self.do_scroll_x = False self.do_scroll_y = True new_box = BoxLayout(size_hint_y=None, orientation='vertical', padding=chatboxpadding, spacing=10) new_box.bind(minimum_height=new_box.setter('height')) with self.canvas.before: Color(subdivisionColor[0], subdivisionColor[1], subdivisionColor[2]) self.rect = RoundedRectangle(size=self.size, pos=self.pos, radius=[10]) # listen to size and position changes self.bind(pos=WidgetCreator.update_rect, size=WidgetCreator.update_rect) self.content = new_box self.add_widget(self.content)
class DiscoveryMixin(): def makeDiscoveryPage(self): # Discovery Page screen = Screen(name='Discovery') self.discoveryScreen = screen layout = BoxLayout(orientation='vertical', spacing=10) screen.add_widget(layout) label = Label( size_hint=(1, None), halign="center", text= 'Browsing your local network.\nWarning: anyone on your network\ncan advertise a site with any title they want.' ) layout.add_widget(self.makeBackButton()) layout.add_widget(label) self.discoveryScroll = ScrollView(size_hint=(1, 1)) self.discoveryListbox = BoxLayout(orientation='vertical', size_hint=(1, None)) self.discoveryListbox.bind( minimum_height=self.discoveryListbox.setter('height')) self.discoveryScroll.add_widget(self.discoveryListbox) layout.add_widget(self.discoveryScroll) return screen def goToDiscovery(self, *a): "Go to the local network discovery page" self.discoveryListbox.clear_widgets() try: hardline.discoveryPeer.search('', n=1) time.sleep(2) for i in hardline.getAllDiscoveries(): info = i self.discoveryListbox.add_widget( MDToolbar(title=str(info.get('title', 'no title')))) l = StackLayout(adaptive_size=True, spacing=8, size_hint=(1, None)) #Need to capture that info in the closures def scope(info): btn = Button(text="Open in Browser") def f(*a): self.openInBrowser("http://" + info['hash'] + ".localhost:7009") btn.bind(on_press=f) l.add_widget(btn) btn = Button(text="Copy URL") def f(*a): try: from kivy.core.clipboard import Clipboard Clipboard.copy("http://" + info['hash'] + ".localhost:7009") except: logging.exception("Could not copy to clipboard") btn.bind(on_press=f) self.localServicesListBox.add_widget( MDToolbar(title=str(info.get('title', 'no title')))) l.add_widget(btn) scope(info) self.discoveryListbox.add_widget(l) lb = self.saneLabel("Hosted By: " + info.get("from_ip", ""), self.discoveryListbox) self.discoveryListbox.add_widget(lb) lb = self.saneLabel("ID: " + info['hash'], self.discoveryListbox) self.discoveryListbox.add_widget(lb) except Exception: logging.info(traceback.format_exc()) self.screenManager.current = "Discovery"
class ToolsAndSettingsMixin(): def goToSettings(self, *a): self.screenManager.current = "Settings" def goToGlobalSettings(self, *a): if os.path.exists(hardline.globalSettingsPath): with open(hardline.globalSettingsPath) as f: globalConfig = toml.load(f) else: globalConfig = {} self.localSettingsBox.clear_widgets() self.localSettingsBox.add_widget( Label(size_hint=(1, 6), halign="center", text='OpenDHT Proxies')) self.localSettingsBox.add_widget( Label(size_hint=(1, None), text='Proxies are tried in order from 1-3')) self.localSettingsBox.add_widget( self.settingButton(globalConfig, "DHTProxy", 'server1')) self.localSettingsBox.add_widget( self.settingButton(globalConfig, "DHTProxy", 'server2')) self.localSettingsBox.add_widget( self.settingButton(globalConfig, "DHTProxy", 'server3')) self.localSettingsBox.add_widget( Label(size_hint=(1, 6), halign="center", text='Stream Server')) self.localSettingsBox.add_widget( Label( size_hint=(1, None), text= 'To allow others to sync to this node as a DrayerDB Stream server, set a server title to expose a service' )) self.localSettingsBox.add_widget( self.settingButton(globalConfig, "DrayerDB", 'serverName')) btn1 = Button(text='Save') def save(*a): with open(hardline.globalSettingsPath, 'w') as f: toml.dump(globalConfig, f) if platform == 'android': self.stop_service() self.start_service() else: daemonconfig.loadDrayerServerConfig() self.screenManager.current = "Main" btn1.bind(on_press=save) self.localSettingsBox.add_widget(btn1) self.screenManager.current = "GlobalSettings" def makeGlobalSettingsPage(self): screen = Screen(name='GlobalSettings') layout = BoxLayout(orientation='vertical', spacing=10) screen.add_widget(layout) layout.add_widget(self.makeBackButton()) self.localSettingsScroll = ScrollView(size_hint=(1, 1)) self.localSettingsBox = BoxLayout(orientation='vertical', size_hint=(1, None), spacing=10) self.localSettingsBox.bind( minimum_height=self.localSettingsBox.setter('height')) self.localSettingsScroll.add_widget(self.localSettingsBox) layout.add_widget(self.localSettingsScroll) return screen def makeSettingsPage(self): page = Screen(name='Settings') layout = BoxLayout(orientation='vertical') page.add_widget(layout) label = MDToolbar(title="Settings and Tools") layout.add_widget(label) layout.add_widget(self.makeBackButton()) log = Button(text='System Logs') btn1 = Button(text='Local Services') label1 = Label(halign="center", text='Share a local webservice with the world') log.bind(on_release=self.gotoLogs) btn1.bind(on_press=self.goToLocalServices) layout.add_widget(log) layout.add_widget(btn1) layout.add_widget(label1) btn = Button(text='Global Settings') btn.bind(on_press=self.goToGlobalSettings) layout.add_widget(btn) # Start/Stop btn3 = Button(text='Stop') btn3.bind(on_press=self.stop_service) label3 = Label( size_hint=(1, None), halign="center", text= 'Stop the background process. It must be running to acess hardline sites. Starting may take a few seconds.' ) layout.add_widget(btn3) layout.add_widget(label3) btn4 = Button(text='Start or Restart.') btn4.bind(on_press=self.start_service) label4 = Label( size_hint=(1, None), halign="center", text='Restart the process. It will show in your notifications.') layout.add_widget(btn4) layout.add_widget(label4) layout.add_widget(Widget()) return page def makeLogsPage(self): screen = Screen(name='Logs') self.servicesScreen = screen layout = BoxLayout(orientation='vertical', spacing=10) screen.add_widget(layout) layout.add_widget(MDToolbar(title="System Logs")) layout.add_widget(self.makeBackButton()) self.logsListBoxScroll = ScrollView(size_hint=(1, 1)) self.logsListBox = BoxLayout(orientation='vertical', size_hint=(1, None), spacing=10) self.logsListBox.bind(minimum_height=self.logsListBox.setter('height')) self.logsListBoxScroll.add_widget(self.logsListBox) layout.add_widget(self.logsListBoxScroll) return screen def gotoLogs(self, *a): self.logsListBox.clear_widgets() try: from kivy.logger import LoggerHistory for i in LoggerHistory.history: self.logsListBox.add_widget( MDTextField(text=str(i.getMessage()), multiline=True, size_hint=(1, None), mode="rectangle")) self.screenManager.current = "Logs" except Exception as e: logging.info(traceback.format_exc())
class StreamsMixin(): #Reuse the same panel for editStream, the main hub for accessing the stream, #and it's core settings def editStream(self, name): if not name in daemonconfig.userDatabases: self.goToStreams() db = daemonconfig.userDatabases[name] c = db.config try: c.add_section("Service") except: pass try: c.add_section("Info") except: pass self.streamEditPanel.clear_widgets() topbar = BoxLayout(size_hint=(1, None), adaptive_height=True, spacing=5) stack = StackLayout(size_hint=(1, None), adaptive_height=True, spacing=5) def upOne(*a): self.goToStreams() btn1 = Button(text='Up') btn1.bind(on_press=upOne) topbar.add_widget(btn1) topbar.add_widget(self.makeBackButton()) self.streamEditPanel.add_widget(topbar) self.streamEditPanel.add_widget(MDToolbar(title=name)) def goHere(): self.editStream(name) self.backStack.append(goHere) self.backStack = self.backStack[-50:] btn2 = Button(text='Notebook View') def goPosts(*a): self.gotoStreamPosts(name) btn2.bind(on_press=goPosts) stack.add_widget(btn2) btn2 = Button(text='Feed View') def goPosts(*a): self.gotoStreamPosts(name, parent=None) btn2.bind(on_press=goPosts) stack.add_widget(btn2) btn2 = Button(text='Stream Settings') def goSettings(*a): self.editStreamSettings(name) btn2.bind(on_press=goSettings) stack.add_widget(btn2) if name.startswith('file:'): btn2 = Button(text='Close Stream') def close(*a): daemonconfig.closeUserDatabase(name) self.goToStreams() btn2.bind(on_press=close) stack.add_widget(btn2) importData = Button(text="Import Data File") def promptSet(*a): try: #Needed for android self.getPermission('files') except: logging.exception("cant ask permission") def f(selection): if selection: def f2(x): if x: with daemonconfig.userDatabases[name]: with open(selection) as f: daemonconfig.userDatabases[ name].importFromToml(f.read()) daemonconfig.userDatabases[name].commit() self.askQuestion("Really import?", "yes", cb=f2) self.openFM.close() from .kivymdfmfork import MDFileManager from . import directories self.openFM = MDFileManager(select_path=f) if os.path.exists("/storage/emulated/0/Documents") and os.access( "/storage/emulated/0/Documents", os.W_OK): self.openFM.show("/storage/emulated/0/Documents") elif os.path.exists( os.path.expanduser("~/Documents")) and os.access( os.path.expanduser("~/Documents"), os.W_OK): self.openFM.show(os.path.expanduser("~/Documents")) else: self.openFM.show(directories.externalStorageDir or directories.settings_path) importData.bind(on_release=promptSet) stack.add_widget(importData) export = Button(text="Export All Posts") def promptSet(*a): from .kivymdfmfork import MDFileManager from .. import directories try: #Needed for android self.getPermission('files') except: logging.exception("cant ask permission") def f(selection): if selection: if not selection.endswith(".toml"): selection = selection + ".toml" def g(a): if a == 'yes': r = daemonconfig.userDatabases[ name].getDocumentsByType('post', parent='') data = daemonconfig.userDatabases[ name].exportRecordSetToTOML( [i['id'] for i in r]) logging.info("Exporting data to:" + selection) with open(selection, 'w') as f: f.write(data) self.openFM.close() if os.path.exists(selection): self.askQuestion("Overwrite?", 'yes', g) else: g('yes') #Autocorrect had some fun with the kivymd devs self.openFM = MDFileManager(select_path=f, save_mode=(name + '.toml')) if os.path.exists("/storage/emulated/0/Documents") and os.access( "/storage/emulated/0/Documents", os.W_OK): self.openFM.show("/storage/emulated/0/Documents") elif os.path.exists( os.path.expanduser("~/Documents")) and os.access( os.path.expanduser("~/Documents"), os.W_OK): self.openFM.show(os.path.expanduser("~/Documents")) else: self.openFM.show(directories.externalStorageDir or directories.settings_path) export.bind(on_release=promptSet) stack.add_widget(export) self.streamEditPanel.add_widget(stack) #Show recent changes no matter where they are in the tree. #TODO needs to be hideable for anti-spoiler purposes in fiction. self.streamEditPanel.add_widget(MDToolbar(title="Recent Changes:")) for i in daemonconfig.userDatabases[name].getDocumentsByType( 'post', orderBy='arrival DESC', limit=5): x = self.makePostWidget(name, i, includeParent=True) self.streamEditPanel.add_widget(x) self.screenManager.current = "EditStream" def showSharingCode(self, name, c, wp=True): if daemonconfig.ddbservice[0]: try: localServer = daemonconfig.ddbservice[0].getSharableURL() except: logging.exception("wtf") else: localServer = '' d = { 'sv': c['Sync'].get('server', '') or localServer, 'vk': c['Sync'].get("syncKey", ''), 'n': name[:24] } if wp: d['sk'] = c['Sync'].get('writePassword', '') else: d['sk'] = '' import json d = json.dumps(d, indent=0, separators=(',', ':')) if wp: self.showQR(d, "Stream Code(full access)") else: self.showQR(d, "Stream Code(readonly)") def editStreamSettings(self, name): db = daemonconfig.userDatabases[name] c = db.config self.streamEditPanel.clear_widgets() self.streamEditPanel.add_widget( Label(size_hint=(1, None), halign="center", text=name)) self.streamEditPanel.add_widget( Label(size_hint=(1, None), halign="center", text="file:" + db.filename)) self.streamEditPanel.add_widget(self.makeBackButton()) def save(*a): logging.info("SAVE BUTTON WAS PRESSED") # On android this is the bg service's job db.saveConfig() if platform == 'android': self.stop_service() self.start_service() else: db.close() daemonconfig.loadUserDatabases( None, only=name, callbackFunction=self.onDrayerRecordChange) def delete(*a): def f(n): if n and n == name: daemonconfig.delDatabase(None, n) if platform == 'android': self.stop_service() self.start_service() self.goToStreams() self.askQuestion("Really delete?", name, f) self.streamEditPanel.add_widget( Label(size_hint=(1, None), halign="center", font_size="24sp", text='Sync')) self.streamEditPanel.add_widget( keyBox := self.settingButton(c, "Sync", "syncKey")) self.streamEditPanel.add_widget( pBox := self.settingButton(c, "Sync", "writePassword")) self.streamEditPanel.add_widget( Label( size_hint=(1, None), halign="center", font_size="12sp", text= 'Keys have a special format, you must use the generator to change them.' )) def promptNewKeys(*a, **k): def makeKeys(a): if a == 'yes': import base64 vk, sk = libnacl.crypto_sign_keypair() vk = base64.b64encode(vk).decode() sk = base64.b64encode(sk).decode() keyBox.text = vk pBox.text = sk self.askQuestion("Overwrite with random keys?", 'yes', makeKeys) keyButton = Button(text='Generate New Keys') keyButton.bind(on_press=promptNewKeys) self.streamEditPanel.add_widget(keyButton) self.streamEditPanel.add_widget( serverBox := self.settingButton(c, "Sync", "server")) self.streamEditPanel.add_widget( Label(size_hint=(1, None), halign="center", text='Do not include the http:// ')) self.streamEditPanel.add_widget( self.settingButton(c, "Sync", "serve", 'yes')) self.streamEditPanel.add_widget( Label(size_hint=(1, None), halign="center", text='Set serve=no to forbid clients to sync')) self.streamEditPanel.add_widget( Label(size_hint=(1, None), halign="center", font_size="24sp", text='Application')) self.streamEditPanel.add_widget( self.settingButton(c, "Application", "notifications", 'no')) def f(*a): def g(a): try: import json a = json.loads(a) serverBox.text = c['Sync'][ 'server'] = a['sv'] or c['Sync']['server'] keyBox.text = c['Sync']['syncKey'] = a['vk'] pBox.text = c['Sync']['writePassword'] = a['sk'] except: pass self.askQuestion("Enter Sharing Code", cb=g, multiline=True) keyButton = Button(text='Load from Code') keyButton.bind(on_press=f) self.streamEditPanel.add_widget(keyButton) def f(*a): self.showSharingCode(name, c) keyButton = Button(text='Show Sharing Code') keyButton.bind(on_press=f) self.streamEditPanel.add_widget(keyButton) def f(*a): self.showSharingCode(name, c, wp=False) keyButton = Button(text='Readonly Sharing Code') keyButton.bind(on_press=f) self.streamEditPanel.add_widget(keyButton) btn1 = Button(text='Save Changes') btn1.bind(on_press=save) self.streamEditPanel.add_widget(btn1) btn2 = Button(text='Delete this stream') btn2.bind(on_press=delete) self.streamEditPanel.add_widget(btn2) def gotoOrphans(*a, **k): self.gotoStreamPosts(name, orphansMode=True) oButton = Button(text='Show Unreachable Garbage') oButton.bind(on_press=gotoOrphans) self.streamEditPanel.add_widget(oButton) noSpreadsheet = Button(text="Spreadsheet on/off") def promptSet(*a): from .kivymdfmfork import MDFileManager from .. import directories try: #Needed for android self.getPermission('files') except: logging.exception("cant ask permission") def f(selection): if selection == 'on': daemonconfig.userDatabases[ name].enableSpreadsheetEval = True else: daemonconfig.userDatabases[ name].enableSpreadsheetEval = False if hasattr(daemonconfig.userDatabases[name], 'enableSpreadsheetEval'): esf = daemonconfig.userDatabases[name].enableSpreadsheetEval else: esf = True self.askQuestion("Allow Spreadsheet Functions?", 'on' if esf else 'off', f) noSpreadsheet.bind(on_release=promptSet) self.streamEditPanel.add_widget(noSpreadsheet) self.streamEditPanel.add_widget( self.saneLabel( "Disabling only takes effect for this session. Use this feature if a stream is loading too slowly, to allow you to fix the offending expression.", self.streamEditPanel)) self.screenManager.current = "EditStream" def makeStreamsPage(self): "Prettu much just an empty page filled in by the specific goto functions" screen = Screen(name='Streams') self.servicesScreen = screen self.streamsEditPanelScroll = ScrollView(size_hint=(1, 1)) self.streamsEditPanel = BoxLayout(orientation='vertical', adaptive_height=True, spacing=5, size_hint=(1, None)) self.streamsEditPanel.bind( minimum_height=self.streamsEditPanel.setter('height')) self.streamsEditPanelScroll.add_widget(self.streamsEditPanel) screen.add_widget(self.streamsEditPanelScroll) return screen def goToStreams(self, *a): "Go to a page wherein we can list user-modifiable services." self.streamsEditPanel.clear_widgets() layout = self.streamsEditPanel bar = BoxLayout(spacing=10, adaptive_height=True, size_hint=(1, None)) stack = StackLayout(spacing=10, adaptive_height=True, size_hint=(1, None)) layout.add_widget(bar) layout.add_widget(MDToolbar(title="My Streams")) layout.add_widget(stack) def upOne(*a): self.gotoMainScreen() btn1 = Button(text='Up') btn1.bind(on_press=upOne) bar.add_widget(btn1) bar.add_widget(self.makeBackButton()) btn2 = Button(text='Create a Stream') btn2.bind(on_press=self.promptAddStream) stack.add_widget(btn2) def f(selection): if selection: dn = 'file:' + os.path.basename(selection) while dn in daemonconfig.userDatabases: dn = dn + '2' try: daemonconfig.loadUserDatabase(selection, dn) self.editStream(dn) except: logging.exception(dn) self.openFM.close() #This lets us view notebook files that aren't installed. def promptOpen(*a): try: #Needed for android self.getPermission('files') except: logging.exception("cant ask permission") from .kivymdfmfork import MDFileManager from . import directories self.openFM = MDFileManager(select_path=f) if os.path.exists("/storage/emulated/0/Documents") and os.access( "/storage/emulated/0/Documents", os.W_OK): self.openFM.show("/storage/emulated/0/Documents") elif os.path.exists( os.path.expanduser("~/Documents")) and os.access( os.path.expanduser("~/Documents"), os.W_OK): self.openFM.show(os.path.expanduser("~/Documents")) else: self.openFM.show(directories.externalStorageDir or directories.settings_path) btn1 = Button(text='Open Book File') btn1.bind(on_press=promptOpen) stack.add_widget(btn1) def goHere(): self.screenManager.current = "Streams" self.backStack.append(goHere) self.backStack = self.backStack[-50:] layout.add_widget(MDToolbar(title="Open Streams:")) try: s = daemonconfig.userDatabases time.sleep(0.5) for i in s: layout.add_widget(self.makeButtonForStream(i)) try: for j in daemonconfig.userDatabases[i].connectedServers: if daemonconfig.userDatabases[i].connectedServers[ j] > (time.time() - (10 * 60)): w = 'online' else: w = 'idle/offline' layout.add_widget( self.saneLabel(j[:28] + ": " + w, layout)) except: logging.exception("Error showing node status") except Exception: logging.info(traceback.format_exc()) self.screenManager.current = "Streams" def makeButtonForStream(self, name): "Make a button that, when pressed, edits the stream in the title" btn = Button(text=name) def f(*a): self.editStream(name) btn.bind(on_press=f) return btn def promptAddStream(self, *a, **k): def f(v): if v: daemonconfig.makeUserDatabase(None, v) self.editStream(v) self.askQuestion("New Stream Name?", cb=f) def makeStreamEditPage(self): "Prettu much just an empty page filled in by the specific goto functions" screen = Screen(name='EditStream') self.servicesScreen = screen self.streamEditPanelScroll = ScrollView(size_hint=(1, 1)) self.streamEditPanel = BoxLayout(orientation='vertical', adaptive_height=True, spacing=5, size_hint=(1, None)) self.streamEditPanel.bind( minimum_height=self.streamEditPanel.setter('height')) self.streamEditPanelScroll.add_widget(self.streamEditPanel) screen.add_widget(self.streamEditPanelScroll) return screen
class ServicesMixin(): def makeLocalServiceEditPage(self): screen = Screen(name='EditLocalService') self.servicesScreen = screen layout = BoxLayout(orientation='vertical', spacing=10) screen.add_widget(layout) self.localServiceEditorName = Label(size_hint=(1, None), halign="center", text="??????????") layout.add_widget(self.makeBackButton()) self.localServiceEditPanelScroll = ScrollView(size_hint=(1, 1)) self.localServiceEditPanel = BoxLayout(orientation='vertical', size_hint=(1, None)) self.localServiceEditPanel.bind( minimum_height=self.localServiceEditPanel.setter('height')) self.localServiceEditPanelScroll.add_widget(self.localServiceEditPanel) layout.add_widget(self.localServiceEditPanelScroll) return screen def editLocalService(self, name, c=None): if not c: c = configparser.RawConfigParser( dict_type=cidict.CaseInsensitiveDict) try: c.add_section("Service") except: pass try: c.add_section("Info") except: pass self.localServiceEditPanel.clear_widgets() self.localServiceEditorName.text = name def save(*a): logging.info("SAVE BUTTON WAS PRESSED") # On android this is the bg service's job daemonconfig.makeUserService( None, name, title=c['Info'].get("title", 'Untitled'), service=c['Service'].get("service", ""), port=c['Service'].get("port", ""), cacheInfo=c['Cache'], noStart=(platform == 'android'), useDHT=c['Access'].get("useDHT", "yes")) if platform == 'android': self.stop_service() self.start_service() self.goToLocalServices() def delete(*a): def f(n): if n and n == name: daemonconfig.delUserService(None, n) if platform == 'android': self.stop_service() self.start_service() self.goToLocalServices() self.askQuestion("Really delete?", name, f) self.localServiceEditPanel.add_widget( Label(size_hint=(1, None), halign="center", text='Service')) self.localServiceEditPanel.add_widget( self.settingButton(c, "Service", "service")) self.localServiceEditPanel.add_widget( self.settingButton(c, "Service", "port")) self.localServiceEditPanel.add_widget( self.settingButton(c, "Info", "title")) self.localServiceEditPanel.add_widget( Label(size_hint=(1, None), halign="center", text='Cache')) self.localServiceEditPanel.add_widget( Label(size_hint=(1, None), text='Cache mode only works for static content')) self.localServiceEditPanel.add_widget( self.settingButton(c, "Cache", "directory")) self.localServiceEditPanel.add_widget( self.settingButton(c, "Cache", "maxAge")) self.localServiceEditPanel.add_widget( Label(size_hint=(1, None), text='Try to refresh after maxAge seconds(default 1 week)')) self.localServiceEditPanel.add_widget( self.settingButton(c, "Cache", "maxSize", '256')) self.localServiceEditPanel.add_widget( Label(size_hint=(1, None), text='Max size to use for the cache in MB')) self.localServiceEditPanel.add_widget( self.settingButton(c, "Cache", "downloadRateLimit", '1200')) self.localServiceEditPanel.add_widget( Label(size_hint=(1, None), text='Max MB per hour to download')) self.localServiceEditPanel.add_widget( self.settingButton(c, "Cache", "dynamicContent", 'no')) self.localServiceEditPanel.add_widget( Label( size_hint=(1, None), text= 'Allow executing code in protected @mako files in the cache dir. yes to enable. Do not use with untrusted @mako' )) self.localServiceEditPanel.add_widget( self.settingButton(c, "Cache", "allowListing", 'no')) self.localServiceEditPanel.add_widget( Label(size_hint=(1, None), text='Allow directory listing of cached content')) self.localServiceEditPanel.add_widget( Label( size_hint=(1, None), text= 'Directory names are subfolders within the HardlineP2P cache folder,\nand can also be used to share\nstatic files by leaving the service blank.' )) self.localServiceEditPanel.add_widget( Label(size_hint=(1, None), halign="center", text='Access Settings')) self.localServiceEditPanel.add_widget( Label(size_hint=(1, None), text='Cache mode only works for static content')) self.localServiceEditPanel.add_widget( self.settingButton(c, "Access", "useDHT", 'yes')) self.localServiceEditPanel.add_widget( Label( size_hint=(1, None), text= 'DHT Discovery uses a proxy server on Android. \nDisabling this saves bandwidth but makes access from outside your network\nunreliable.' )) btn1 = Button(text='Save Changes') btn1.bind(on_press=save) self.localServiceEditPanel.add_widget(btn1) btn2 = Button(text='Delete this service') btn2.bind(on_press=delete) self.localServiceEditPanel.add_widget(btn2) self.screenManager.current = "EditLocalService" def makeButtonForLocalService(self, name, c=None): "Make a button that, when pressed, edits the local service in the title" btn = Button(text=name) def f(*a): self.editLocalService(name, c) btn.bind(on_press=f) return btn def makeLocalServicesPage(self): screen = Screen(name='LocalServices') self.servicesScreen = screen layout = BoxLayout(orientation='vertical', spacing=10) screen.add_widget(layout) label = Label( size_hint=(1, None), halign="center", text= 'WARNING: Running a local service may use a lot of data and battery.\nChanges may require service restart.' ) labelw = Label( size_hint=(1, None), halign="center", text= 'WARNING 2: This app currently prefers the external SD card for almost everything including the keys.' ) layout.add_widget(self.makeBackButton()) layout.add_widget(label) layout.add_widget(labelw) btn2 = Button(text='Create a service') btn2.bind(on_press=self.promptAddService) layout.add_widget(btn2) self.localServicesListBoxScroll = ScrollView(size_hint=(1, 1)) self.localServicesListBox = BoxLayout(orientation='vertical', size_hint=(1, None), spacing=10) self.localServicesListBox.bind( minimum_height=self.localServicesListBox.setter('height')) self.localServicesListBoxScroll.add_widget(self.localServicesListBox) layout.add_widget(self.localServicesListBoxScroll) return screen def promptAddService(self, *a, **k): def f(v): if v: self.editLocalService(v) self.askQuestion("New Service filename?", cb=f) def goToLocalServices(self, *a): "Go to a page wherein we can list user-modifiable services." self.localServicesListBox.clear_widgets() try: s = daemonconfig.listServices(None) time.sleep(0.5) for i in s: self.localServicesListBox.add_widget( self.makeButtonForLocalService(i, s[i])) except Exception: logging.info(traceback.format_exc()) self.screenManager.current = "LocalServices"