andreped commited on
Commit
7bbc6b6
·
unverified ·
2 Parent(s): e32d549 3a5a033

Merge pull request #11 from andreped/ui-ux-fixes

Browse files

Improved UI; added support for likes; refactored widgets to be more twitter-like

app.py CHANGED
@@ -1,10 +1,17 @@
1
  import streamlit as st
2
 
3
  from postly.clients.singleton_client import SingletonPostlyClient
 
4
 
5
  # Initialize the PostlyClient singleton
6
  client = SingletonPostlyClient.get_instance()
7
 
 
 
 
 
 
 
8
  # Initialize user session state
9
  if "logged_in" not in st.session_state:
10
  st.session_state.logged_in = False
@@ -14,8 +21,8 @@ if "current_user" not in st.session_state:
14
 
15
  def register():
16
  st.title("Register")
17
- user_name = st.text_input("Enter user name")
18
- password = st.text_input("Enter password", type="password")
19
  if st.button("Register"):
20
  if user_name and password:
21
  try:
@@ -32,8 +39,8 @@ def register():
32
 
33
  def login():
34
  st.title("Login")
35
- user_name = st.text_input("Enter user name")
36
- password = st.text_input("Enter password", type="password")
37
  if st.button("Login"):
38
  if client.authenticate_user(user_name, password):
39
  st.session_state.logged_in = True
@@ -52,7 +59,7 @@ def logout():
52
 
53
 
54
  def delete_own_user():
55
- st.title("Delete Account")
56
  if st.button("Delete Account"):
57
  try:
58
  client.delete_user(st.session_state.current_user)
@@ -62,19 +69,8 @@ def delete_own_user():
62
  st.error(f"Error: {e}")
63
 
64
 
65
- def add_post():
66
- st.title("Add Post")
67
- post_text = st.text_area("Enter post text")
68
- if st.button("Add Post"):
69
- try:
70
- client.add_post(st.session_state.current_user, post_text)
71
- st.success("Post added successfully.")
72
- except Exception as e:
73
- st.error(f"Error: {e}")
74
-
75
-
76
  def get_posts_for_user():
77
- st.title("Get Posts for User")
78
  users = client.get_users()
79
  user_name = st.selectbox("Select user name", users)
80
  if st.button("Get Posts"):
@@ -82,24 +78,24 @@ def get_posts_for_user():
82
  posts = client.get_posts_for_user(user_name)
83
  st.write(f"Posts for user '{user_name}':")
84
  for post in posts:
85
- st.write(post)
86
  except KeyError as e:
87
  st.error(f"Error: {e}")
88
 
89
 
90
  def get_posts_for_topic():
91
- st.title("Get Posts for Topic")
92
  topics = client.get_topics()
93
  topic = st.selectbox("Enter topic", topics)
94
  if st.button("Get Posts"):
95
  posts = client.get_posts_for_topic(topic)
96
  st.write(f"Posts for topic '{topic}':")
97
  for post in posts:
98
- st.write(post)
99
 
100
 
101
  def get_trending_topics():
102
- st.title("Get Trending Topics")
103
  current_timestamp = client.get_current_timestamp()
104
  from_timestamp = st.number_input("Enter from timestamp", min_value=0, step=1)
105
  to_timestamp = st.number_input(
@@ -116,21 +112,51 @@ def get_trending_topics():
116
 
117
 
118
  def get_all_posts():
119
- st.title("All Posts")
 
 
 
 
 
 
 
 
 
 
 
 
120
  posts = client.get_posts()
121
  all_posts = []
122
  for user_name, user_posts in posts.items():
123
  for post in user_posts:
124
  all_posts.append((user_name, post))
125
- sorted_posts = sorted(all_posts, key=lambda x: x[1].timestamp)
126
  for user_name, post in sorted_posts:
127
- st.markdown(f"**{user_name}**")
128
- st.markdown(f"{post.content}")
129
- st.markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
 
132
  def main():
133
- st.sidebar.title("Postly\nSimple social media platform")
 
134
  if st.session_state.logged_in:
135
  st.sidebar.write(f"Logged in as: {st.session_state.current_user}")
136
  if st.sidebar.button("Logout"):
@@ -138,18 +164,17 @@ def main():
138
  page = st.sidebar.selectbox(
139
  "Choose an action",
140
  [
141
- "Add Post",
142
  "Delete Account",
143
  "Get Posts for User",
144
  "Get Posts for Topic",
145
  "Get Trending Topics",
146
- "View All Posts",
147
  ],
148
- index=5,
149
  )
150
 
151
- if page == "Add Post":
152
- add_post()
153
  elif page == "Delete Account":
154
  delete_own_user()
155
  elif page == "Get Posts for User":
@@ -158,14 +183,27 @@ def main():
158
  get_posts_for_topic()
159
  elif page == "Get Trending Topics":
160
  get_trending_topics()
161
- elif page == "View All Posts":
162
- get_all_posts()
163
  else:
164
- page = st.sidebar.selectbox("Choose an action", ["Login", "Register"], index=0)
165
- if page == "Login":
166
- login()
167
- elif page == "Register":
168
  register()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
 
170
 
171
  if __name__ == "__main__":
 
1
  import streamlit as st
2
 
3
  from postly.clients.singleton_client import SingletonPostlyClient
4
+ from postly.common.css import get_theme
5
 
6
  # Initialize the PostlyClient singleton
7
  client = SingletonPostlyClient.get_instance()
8
 
9
+ # Custom CSS for Twitter-like feel
10
+ st.markdown(
11
+ get_theme(),
12
+ unsafe_allow_html=True,
13
+ )
14
+
15
  # Initialize user session state
16
  if "logged_in" not in st.session_state:
17
  st.session_state.logged_in = False
 
21
 
22
  def register():
23
  st.title("Register")
24
+ user_name = st.text_input("Enter user name", placeholder="Username")
25
+ password = st.text_input("Enter password", type="password", placeholder="Password")
26
  if st.button("Register"):
27
  if user_name and password:
28
  try:
 
39
 
40
  def login():
41
  st.title("Login")
42
+ user_name = st.text_input("Enter user name", placeholder="Username")
43
+ password = st.text_input("Enter password", type="password", placeholder="Password")
44
  if st.button("Login"):
45
  if client.authenticate_user(user_name, password):
46
  st.session_state.logged_in = True
 
59
 
60
 
61
  def delete_own_user():
62
+ st.title("Delete Account")
63
  if st.button("Delete Account"):
64
  try:
65
  client.delete_user(st.session_state.current_user)
 
69
  st.error(f"Error: {e}")
70
 
71
 
 
 
 
 
 
 
 
 
 
 
 
72
  def get_posts_for_user():
73
+ st.title("Get Posts for User 🔎")
74
  users = client.get_users()
75
  user_name = st.selectbox("Select user name", users)
76
  if st.button("Get Posts"):
 
78
  posts = client.get_posts_for_user(user_name)
79
  st.write(f"Posts for user '{user_name}':")
80
  for post in posts:
81
+ st.markdown(post)
82
  except KeyError as e:
83
  st.error(f"Error: {e}")
84
 
85
 
86
  def get_posts_for_topic():
87
+ st.title("Get Posts for Topic 🔎")
88
  topics = client.get_topics()
89
  topic = st.selectbox("Enter topic", topics)
90
  if st.button("Get Posts"):
91
  posts = client.get_posts_for_topic(topic)
92
  st.write(f"Posts for topic '{topic}':")
93
  for post in posts:
94
+ st.markdown(post)
95
 
96
 
97
  def get_trending_topics():
98
+ st.title("Get Trending Topics 📊")
99
  current_timestamp = client.get_current_timestamp()
100
  from_timestamp = st.number_input("Enter from timestamp", min_value=0, step=1)
101
  to_timestamp = st.number_input(
 
112
 
113
 
114
  def get_all_posts():
115
+ st.title("Feed")
116
+
117
+ # Add post section at the top
118
+ post_text = st.text_area("What's happening? 💬", key="new_post_text")
119
+ if st.button("Add Post 🖊️"):
120
+ try:
121
+ client.add_post(st.session_state.current_user, post_text)
122
+ st.success("Post added successfully.")
123
+ st.rerun()
124
+ except Exception as e:
125
+ st.error(f"Error: {e}")
126
+
127
+ # Display all posts
128
  posts = client.get_posts()
129
  all_posts = []
130
  for user_name, user_posts in posts.items():
131
  for post in user_posts:
132
  all_posts.append((user_name, post))
133
+ sorted_posts = sorted(all_posts, key=lambda x: x[1].timestamp)[::-1]
134
  for user_name, post in sorted_posts:
135
+ liked = st.session_state.current_user in post.liked_by
136
+ like_button_label = "👍" if not liked else "👎"
137
+
138
+ col1, col2 = st.columns([4, 1])
139
+ with col1:
140
+ st.markdown(
141
+ f"""
142
+ <div class="post-container">
143
+ <div class="post-header">{user_name}</div>
144
+ <div class="post-content">{post.content}</div>
145
+ <div class="post-timestamp">{post.timestamp}</div>
146
+ <div class="post-likes">Likes: {post.likes}</div>
147
+ </div>
148
+ """,
149
+ unsafe_allow_html=True,
150
+ )
151
+ with col2:
152
+ if st.button(like_button_label, key=f"like_{post.timestamp}"):
153
+ client.like_post(st.session_state.current_user, post.timestamp)
154
+ st.rerun()
155
 
156
 
157
  def main():
158
+ st.sidebar.title("Postly 📝\nSimple social media platform")
159
+
160
  if st.session_state.logged_in:
161
  st.sidebar.write(f"Logged in as: {st.session_state.current_user}")
162
  if st.sidebar.button("Logout"):
 
164
  page = st.sidebar.selectbox(
165
  "Choose an action",
166
  [
167
+ "View All Posts",
168
  "Delete Account",
169
  "Get Posts for User",
170
  "Get Posts for Topic",
171
  "Get Trending Topics",
 
172
  ],
173
+ index=0,
174
  )
175
 
176
+ if page == "View All Posts":
177
+ get_all_posts()
178
  elif page == "Delete Account":
179
  delete_own_user()
180
  elif page == "Get Posts for User":
 
183
  get_posts_for_topic()
184
  elif page == "Get Trending Topics":
185
  get_trending_topics()
 
 
186
  else:
187
+ page = st.sidebar.selectbox("Choose an action", ["Register", "Login"], index=0)
188
+ if page == "Register":
 
 
189
  register()
190
+ elif page == "Login":
191
+ login()
192
+
193
+ st.sidebar.markdown(
194
+ """
195
+ **About Postly**
196
+
197
+ Welcome to Postly, a simple social media platform created for fun.
198
+ This app allows different users to share posts and like each other's posts.
199
+
200
+ **Important Information**
201
+
202
+ - The entire app is kept in global memory for all users accessing the app on the same instance.
203
+ - Do not use a username and password actually used with any other apps.
204
+ - We hash the password, but no real attempt to make a bulletproof solution was made.
205
+ """
206
+ )
207
 
208
 
209
  if __name__ == "__main__":
postly/clients/postly_client.py CHANGED
@@ -214,3 +214,36 @@ class PostlyClient:
214
 
215
  # retrieve top topics in descending order
216
  return [topic for topic, _ in topics_frequency.most_common()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
214
 
215
  # retrieve top topics in descending order
216
  return [topic for topic, _ in topics_frequency.most_common()]
217
+
218
+ def like_post(self, user_name: str, post_id: int) -> None:
219
+ """
220
+ Like or unlike a post.
221
+
222
+ Args:
223
+ user_name: The name of the user liking the post.
224
+ post_id: The ID of the post to like or unlike.
225
+ Returns:
226
+ None
227
+ """
228
+ post = self.get_post_by_id(post_id)
229
+ if user_name in post.liked_by:
230
+ post.liked_by.remove(user_name)
231
+ post.likes -= 1
232
+ else:
233
+ post.liked_by.add(user_name)
234
+ post.likes += 1
235
+
236
+ def get_post_by_id(self, post_id: int) -> Post:
237
+ """
238
+ Get a post by its ID.
239
+
240
+ Args:
241
+ post_id: The ID of the post to retrieve.
242
+ Returns:
243
+ The post with the given ID.
244
+ """
245
+ for user_posts in self.userPosts.values():
246
+ for post in user_posts:
247
+ if post.timestamp == post_id:
248
+ return post
249
+ raise KeyError("Post not found")
postly/common/css.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ def get_theme():
2
+ return """
3
+ <style>
4
+ body {
5
+ background-color: #ffffff;
6
+ color: #1DA1F2;
7
+ }
8
+ .stButton>button {
9
+ background-color: #1DA1F2;
10
+ color: white;
11
+ }
12
+ .stTextInput>div>div>input {
13
+ border: 1px solid #1DA1F2;
14
+ }
15
+ .stTextArea>div>div>textarea {
16
+ border: 1px solid #1DA1F2;
17
+ }
18
+ .stSelectbox>div>div>div>div {
19
+ border: 1px solid #1DA1F2;
20
+ }
21
+ .post-container {
22
+ border: 1px solid #1DA1F2;
23
+ border-radius: 10px;
24
+ padding: 10px;
25
+ margin-bottom: 10px;
26
+ background-color: #f5f8fa;
27
+ min-height: 100px; /* Adjust this value as needed */
28
+ position: relative; /* Ensure absolute positioning works within this container */
29
+ }
30
+ .post-header {
31
+ font-weight: bold;
32
+ color: #1DA1F2;
33
+ }
34
+ .post-content {
35
+ color: #14171A;
36
+ }
37
+ .post-likes {
38
+ color: #657786;
39
+ font-size: 14px;
40
+ position: absolute;
41
+ bottom: 10px;
42
+ right: 10px;
43
+ }
44
+ .like-button {
45
+ background-color: transparent;
46
+ border: none;
47
+ color: #1DA1F2;
48
+ cursor: pointer;
49
+ font-size: 20px;
50
+ }
51
+ </style>
52
+ """
postly/common/models.py CHANGED
@@ -1,5 +1,7 @@
1
  from dataclasses import dataclass
 
2
  from typing import List
 
3
 
4
  from pydantic import BaseModel
5
 
@@ -8,6 +10,8 @@ class StrictPost(BaseModel):
8
  content: str
9
  timestamp: int
10
  topics: List[str]
 
 
11
 
12
 
13
  @dataclass
@@ -15,11 +19,25 @@ class Post:
15
  content: str
16
  timestamp: int
17
  topics: List[str]
 
 
18
 
19
 
20
  if __name__ == "__main__":
21
  # this should be OK, as not strictly typed
22
- Post(content=1, timestamp=1, topics=["1"])
 
 
 
23
 
24
  # this should result in a validation error, as pydantic enforces strict typing on runtime
25
- StrictPost(content=1, timestamp=1, topics=["1"])
 
 
 
 
 
 
 
 
 
 
1
  from dataclasses import dataclass
2
+ from dataclasses import field
3
  from typing import List
4
+ from typing import Set
5
 
6
  from pydantic import BaseModel
7
 
 
10
  content: str
11
  timestamp: int
12
  topics: List[str]
13
+ likes: int = 0
14
+ liked_by: Set[str] = set()
15
 
16
 
17
  @dataclass
 
19
  content: str
20
  timestamp: int
21
  topics: List[str]
22
+ likes: int = 0
23
+ liked_by: Set[str] = field(default_factory=set)
24
 
25
 
26
  if __name__ == "__main__":
27
  # this should be OK, as not strictly typed
28
+ post = Post(content="1", timestamp=1, topics=["1"])
29
+ post.likes += 1
30
+ post.liked_by.add("user1")
31
+ print(post)
32
 
33
  # this should result in a validation error, as pydantic enforces strict typing on runtime
34
+ try:
35
+ strict_post = StrictPost(content=1, timestamp=1, topics=["1"])
36
+ except Exception as e:
37
+ print(f"Validation error: {e}")
38
+
39
+ # this should be OK, as strictly typed
40
+ strict_post = StrictPost(content="1", timestamp=1, topics=["1"])
41
+ strict_post.likes += 1
42
+ strict_post.liked_by.add("user1")
43
+ print(strict_post)