import os,sys; sys.path.append(os.path.abspath(os.path.join(os.path.abspath(os.path.join(os.path.dirname(__file__),'..')),'..'))) from comrad import * from screens.base import BaseScreen from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.chip import MDChip from kivymd.uix.textfield import MDTextField from kivymd.uix.button import MDRectangleFlatButton,MDRectangleFlatIconButton,MDIconButton from kivymd.uix.label import MDLabel, MDIcon from kivy.uix.image import AsyncImage, Image from kivy.metrics import dp from kivy.properties import StringProperty from kivy.core.window import Window from kivy.core.image import Image as CoreImage import io,asyncio from kivy.uix.carousel import Carousel from screens.feed.feed import PostCard from kivy.clock import Clock from functools import partial from copy import copy,deepcopy from kivy.animation import Animation from main import MyLabel,COLOR_ICON from misc import * import shutil class ProfileAvatar(Image): def on_touch_down(self, touch): if self.collide_point(*touch.pos): if self.screen.carousel.index and self.screen.carousel.slides: start = self.screen.carousel.slides[0] start.opacity=0 self.screen.carousel.index=0 anim = Animation(opacity=1, duration=0.1) anim.start(start) elif self.screen.app.username == self.screen.app.comrad.name: self.choose() def choose(self): try: self.choose_native() except (NotImplementedError,TypeError,OSError) as e: self.choose_nonnative() def choose_native(self): from plyer import filechooser filechooser.open_file(on_selection=self.handle_selection) def choose_nonnative(self): import wx app = wx.App(None) wildcard = "PNG Files (*.png)|*.png|JPEG Files (*.jpg/*.jpeg)|*.jpg;*.jpeg|GIF Files (*.gif)|*.gif" style = wx.FD_OPEN | wx.FD_FILE_MUST_EXIST dialog = wx.FileDialog(None, 'Select Data File',wildcard=wildcard,style=style) if dialog.ShowModal() == wx.ID_OK: path = dialog.GetPath() else: path = None dialog.Destroy() self.handle_selection(path) def handle_selection(self, selection): if selection and type(selection)==list: fnfn = selection[0] _,ext=os.path.splitext(fnfn) if ext[1:] in ALLOWED_IMG_EXT: #avatar_fnfn = os.path.join(PATH_AVATAR,self.com) self.parent.parent.parent.parent.parent.change_avatar(fnfn) class LayoutAvatar(MDBoxLayout): pass class AuthorInfoLayout(MDBoxLayout): pass class LayoutCover(MDBoxLayout): source=StringProperty() pass class CoverImage(Image): pass def binarize_image(image, threshold=200): """Binarize an image.""" image = image.convert('L') # convert image to monochrome import numpy image = numpy.array(image) image = binarize_array(image, threshold) imsave(target_path, image) return image def binarize_array(numpy_array, threshold=200): """Binarize a numpy array.""" import numpy for i in range(len(numpy_array)): for j in range(len(numpy_array[0])): if numpy_array[i][j] > threshold: numpy_array[i][j] = 255 else: numpy_array[i][j] = 0 return numpy_array def crop_square(pil_img, crop_width, crop_height): img_width, img_height = pil_img.size return pil_img.crop(((img_width - crop_width) // 2, (img_height - crop_height) // 2, (img_width + crop_width) // 2, (img_height + crop_height) // 2)) def circularize_img(img_fn, width, im=None, do_crop=True,bw=False,resize=True,circularize=True): from PIL import Image, ImageOps, ImageDraw from kivy.app import App import numpy as np log=App.get_running_app().log #if not im: im = Image.open(img_fn).convert('RGB') log('??',im) # get center if do_crop: im = crop_square(im, width, width) if resize: im = im.resize((width,width)) if bw: thresh = 175 fn = lambda x : 255 if x > thresh else 0 im = im.convert('L').point(fn, mode='1').convert('RGB') orig_color = (255,255,255) replacement_color = COLOR_ICON #(255,0,0) # img = im.convert('RGB') data = np.array(im) data[(data == orig_color).all(axis = -1)] = replacement_color im = Image.fromarray(data, mode='RGB').convert('RGBA') if circularize_img: bigsize = (im.size[0] * 3, im.size[1] * 3) mask = Image.new('L', bigsize, 0) draw = ImageDraw.Draw(mask) draw.ellipse((0, 0) + bigsize, fill=255) mask = mask.resize(im.size, Image.ANTIALIAS) im.putalpha(mask) # give back bytes log('!!',im) output = ImageOps.fit(im, mask.size, centering=(0.5, 0.5)) imgByteArr = io.BytesIO() output.save(imgByteArr, format='PNG') # imgByteArr = imgByteArr.getvalue() imgByteArr.seek(0) return imgByteArr # output.putalpha(mask) # output.save('output.png') # background = Image.open('back.jpg') # background.paste(im, (150, 10), im) # background.save('overlap.png') # return output class ProfilePageLayout(MDBoxLayout): pass class FollowerLayout(MDBoxLayout): pass class AuthorName(MyLabel): pass class AuthorUsername(MyLabel): pass class AuthorDesc(MyLabel): pass class AuthorPronouns(MyChip): pass class AuthorPlace(MyChip): pass class AuthorWebsite(MyChip): pass class AuthorFollowers(MyChip): pass class AuthorFollowing(MyChip): pass def update_screen_on_carousel_move(self,dt,width=75): # screen.author_name.text=str(screen.carousel.index) # avatar_layout = copy(screen.avatar_layout) # avatar_layout.width=dp(100) # avatar_layout.height=dp(100) if not self.do_update_screen_on_carousel_move: return if self.carousel.index: if not hasattr(self,'avatar_layout_small'): self.avatar_img.seek(0) img,byte,avatar,avatar_layout = self.make_profile_img(width,do_crop=False,bw=True,circularize=False) avatar.screen = self avatar_layout.pos_hint = {'right':0.995, 'top':0.995} avatar_layout.opacity=0 # avatar_layout.animate() self.add_widget(avatar_layout) self.avatar_layout_small=avatar_layout self.avatar_layout_small_visible=False if not self.avatar_layout_small_visible: self.avatar_layout_small_visible=True anim = Animation(opacity=1, duration=0.25) anim.start(self.avatar_layout_small) else: if hasattr(self,'avatar_layout_small'): if self.avatar_layout_small_visible: self.avatar_layout_small_visible=False anim = Animation(opacity=0, duration=0.25) anim.start(self.avatar_layout_small) # self.remove_widget(self.avatar_layout_small) # del self.avatar_layout_small # avatar_layout = self.avatar_layout # self.remove_widget(avatar_layout) # self.add_widget(avatar_layout) from screens.base import ProtectedScreen class ProfileScreen(ProtectedScreen): username = None clock_scheduled=None do_update_screen_on_carousel_move=True def make_profile_img(self,width,do_crop=True,circ_img=None,bw=False,circularize=True): img_src = os.path.join(PATH_AVATARS, f'{self.app.username}.png') if not os.path.exists(img_src): img_src = os.path.join(PATH_GUI_ASSETS, 'avatars', f'{self.app.username}.png') if not os.path.exists(img_src): img_src=PATH_DEFAULT_AVATAR circ_img = circularize_img(img_src,width,do_crop=do_crop,bw=bw,circularize=circularize) avatar_layout = LayoutAvatar() byte=io.BytesIO(circ_img.read()) img = CoreImage(byte,ext='png') avatar = ProfileAvatar() avatar.texture = img.texture avatar_layout.height=dp(width) avatar_layout.width=dp(width) avatar_layout.add_widget(avatar) return (circ_img,byte,avatar,avatar_layout) def change_avatar(self,fnfn,width=AVATAR_WIDTH): # raise Exception(f'Got filename! {fnfn}') if not os.path.exists(fnfn): return ext=os.path.splitext(fnfn)[1] ofnfn=os.path.join(PATH_AVATARS,self.app.username+ext) shutil.copyfile(fnfn,ofnfn) # re-get circular image # self.avatar_layout.remove_widget(self.avatar) # self.on_pre_enter() self.page_layout.remove_widget(self.avatar_layout) self.avatar_img, self.avatar_img_bytes, self.avatar, self.avatar_layout = \ self.make_profile_img(width) self.avatar.screen = self self.page_layout.add_widget(self.avatar_layout,1) # self.avatar_layout.add_widget(self.avatar) if hasattr(self,'avatar_layout_small'): del self.avatar_layout_small def on_pre_enter(self, width=AVATAR_WIDTH): if not super().on_pre_enter(): return self.do_update_screen_on_carousel_move=True if not self.clock_scheduled: Clock.schedule_interval(partial(update_screen_on_carousel_move, self), 0.1) self.clock_scheduled=True # clear if hasattr(self,'carousel'): for post in self.posts: self.carousel.remove_widget(post) self.remove_widget(self.carousel) del self.carousel self.posts=[] self.carousel = Carousel() self.carousel.direction='right' self.carousel.loop=True self.posts=[] # get circular image self.avatar_img, self.avatar_img_bytes, self.avatar, self.avatar_layout = \ self.make_profile_img(width) self.avatar.screen = self ## author info self.author_info_layout = AuthorInfoLayout() #self.app.name_irl = 'Marx Zuckerberg' self.app.name_irl = 'Comrad @'+self.app.username if hasattr(self.app,'name_irl'): self.author_name_irl = AuthorName(text=self.app.name_irl) self.author_name_irl.font_name = 'assets/font.otf' self.author_name_irl.font_size = '28sp' self.author_info_layout.add_widget(self.author_name_irl) self.author_name = AuthorUsername(text='@'+self.app.username) self.author_name.font_name = 'assets/font.otf' self.author_name.font_size = '20sp' # self.author_info_layout.add_widget(self.author_name) # desc from us self.log('do I know where ') if not self.app.comrad.exists_locally(self.app.username): author_desc=f'Comrad @{self.app.username} is not a contact of yours.' self.author_desc = AuthorDesc(text=author_desc) self.author_desc.font_name='assets/font.otf' self.author_desc.font_size='18sp' self.author_info_layout.add_widget(self.author_desc) # this is a contact else: ## AUTHOR DESCRIPTION author_desc=f'... etc ...' self.author_desc = AuthorDesc(text=author_desc) self.author_desc.font_name='assets/font.otf' self.author_desc.font_size='18sp' # self.author_desc.halign='left' ## Pronouns self.author_pronouns = AuthorPronouns(label='they/them',icon='gender-transgender') ## AUTHOR PLACE self.author_place = AuthorPlace(label='Deterritorialized',icon='map-marker-outline') ## Website self.author_website = AuthorWebsite(label='website.org', icon='link-variant') ## Followers self.follower_layout = FollowerLayout() # self.author_followers = AuthorFollowers(label='13 followers',icon='account-arrow-left') self.author_following = AuthorFollowing(label='13 comrades',icon='account-multiple') ## add to layout self.author_info_layout.add_widget(self.author_desc) self.author_info_layout.add_widget(self.author_pronouns) self.author_info_layout.add_widget(self.author_place) # self.author_info_layout.add_widget(self.author_website) self.follower_layout.add_widget(self.author_following) # self.follower_layout.add_widget(self.author_followers) self.author_info_layout.add_widget(self.follower_layout) # class AuthorPlace(MDLabel): pass # class AuthorWebsite(MDLabel): pass # class AuthorFollowers(MDLabel): pass # class AuthorFollowing(MDLabel): pass ## add root widgets self.page_layout = ProfilePageLayout() self.page_layout.add_widget(self.avatar_layout) self.page_layout.add_widget(self.author_info_layout) self.add_widget(self.carousel) self.carousel.add_widget(self.page_layout) ## add posts asyncio.create_task(self.add_author_posts()) async def add_author_posts(self): lim=25 self.log('USERNAME:',self.app.username) posts=self.app.comrad.sent_posts( username=self.app.username ) self.log('OUTBOX:',len(posts),'posts') for i,post in enumerate(posts): if i>lim: break data = { 'author':post.from_name, 'to_name':post.to_name, 'content':post.msg.get('txt') if type(post.msg)==dict else str(post.msg), 'timestamp':post.timestamp } post_obj = PostCard(data) self.log('sent post!',post) self.posts.append(post_obj) self.carousel.add_widget(post_obj) def on_pre_leave(self): self.app.username=self.app.comrad.name # self.avatar_layout_small_visible=False if hasattr(self,'avatar_layout_small'): self.remove_widget(self.avatar_layout_small) del self.avatar_layout_small self.do_update_screen_on_carousel_move=False # def on_touch_move(self, ent): # if self.carousel.index: # self.author_name.text='moved!' # else: # self.author_name.text=self.username