You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Comrad/comrad/app/screens/profile/profile.py

409 lines
14 KiB
Python

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
4 years ago
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 *
4 years ago
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)
4 years ago
elif self.screen.app.username == self.screen.app.comrad.name:
4 years ago
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):
4 years ago
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
4 years ago
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))
4 years ago
def circularize_img(img_fn, width, im=None, do_crop=True,bw=False,resize=True,circularize=True):
from PIL import Image, ImageOps, ImageDraw
4 years ago
from kivy.app import App
import numpy as np
log=App.get_running_app().log
4 years ago
#if not im:
4 years ago
im = Image.open(img_fn).convert('RGB')
4 years ago
log('??',im)
# get center
if do_crop: im = crop_square(im, width, width)
4 years ago
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)
4 years ago
# 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)
4 years ago
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)
4 years ago
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
4 years ago
do_update_screen_on_carousel_move=True
4 years ago
def make_profile_img(self,width,do_crop=True,circ_img=None,bw=False,circularize=True):
4 years ago
img_src = os.path.join(PATH_AVATARS, f'{self.app.username}.png')
4 years ago
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
4 years ago
4 years ago
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)
4 years ago
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]
4 years ago
ofnfn=os.path.join(PATH_AVATARS,self.app.username+ext)
4 years ago
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)
4 years ago
if hasattr(self,'avatar_layout_small'):
del self.avatar_layout_small
4 years ago
def on_pre_enter(self, width=AVATAR_WIDTH):
if not super().on_pre_enter(): return
4 years ago
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()
4 years ago
#self.app.name_irl = 'Marx Zuckerberg'
self.app.name_irl = 'Comrad @'+self.app.username
4 years ago
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)
4 years ago
self.author_name = AuthorUsername(text='@'+self.app.username)
self.author_name.font_name = 'assets/font.otf'
self.author_name.font_size = '20sp'
4 years ago
# self.author_info_layout.add_widget(self.author_name)
4 years ago
# desc from us
self.log('do I know where ')
4 years ago
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)
4 years ago
# 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
4 years ago
asyncio.create_task(self.add_author_posts())
4 years ago
async def add_author_posts(self):
lim=25
4 years ago
4 years ago
self.log('USERNAME:',self.app.username)
posts=self.app.comrad.sent_posts(
username=self.app.username
)
self.log('OUTBOX:',len(posts),'posts')
4 years ago
4 years ago
for i,post in enumerate(posts):
if i>lim: break
4 years ago
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)
4 years ago
4 years ago
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