diff --git a/README.md b/README.md index ed0090b..836a0e8 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,4 @@ Here are the different scenarios of interactions that occur in the data: | X | X | X | | The user sent an outgoing like, the other person liked them back, at least one message was exchanged | | | X | X | | The user received an incoming like, the user liked the other person back and at least one message was exchanged | | | | | X | The match was removed or "unmatched", can't tell who unmatched who. For some reason, a lot of these exist without any other information and there is no way to tell which interaction it was originally linked to | -| | X | | X | The user received an incoming like, the user liked the other person back, no messages were exchanged, and the match was removed | - -## What's next -I have a long list of enhancements and fixes that I want to do to the application to make it better. To see what's on deck, check out the [Projects](https://github.com/users/smpotts/projects/2) tab in the repo. \ No newline at end of file +| | X | | X | The user received an incoming like, the user liked the other person back, no messages were exchanged, and the match was removed | \ No newline at end of file diff --git a/app/app.py b/app/app.py index 0d110e4..ac302b0 100644 --- a/app/app.py +++ b/app/app.py @@ -7,18 +7,16 @@ import dash_mantine_components as dmc from flask import Flask import dash -from dash import Dash, dcc, html, Input, Output, State, callback +from dash import Dash, dcc, html import os -import base64 import pages.MatchPage as MatchPage import pages.UserPage as UserPage import pages.HomePage as HomePage +import pages.InfoPage as InfoPage from tools.Logger import logger -USER_FILE_UPLOAD_DIRECTORY = "../data/app_uploaded_files" - external_stylesheets = [dmc.theme.DEFAULT_COLORS] server = Flask(__name__) app = Dash(__name__, server=server, use_pages=True, external_stylesheets=external_stylesheets) @@ -26,96 +24,48 @@ dash.register_page("home", path='/', layout=HomePage.layout) dash.register_page("matches", path='/matches', layout=MatchPage.layout) dash.register_page("user", path='/user', layout=UserPage.layout) +dash.register_page("info", path='/info', layout=InfoPage.layout) + +def get_additional_text(page_name): + """Helper function to provide context about the different hyperlinks based on the page name.""" + if page_name == "Info": + return "Discover detailed insights about the app's features and functionality. This section offers a comprehensive" \ + " overview of how the project works, what data is available to the user, and how to navigate the app for the best experience." + elif page_name == "Matches": + return "Explore in-depth analyses of the users matches and interactions. This section reveals patterns in the user's matching " \ + "behavior, preferences, and key factors that influence successful connections with potential matches." + elif page_name == "User": + return "Analyze the user's personal profile and preferences. This section contains insights into how the user's " \ + "profile is presented, the preferences they've set, and how their interactions shape their experience on the app." app.layout = html.Div([ dmc.Title('Hinge Data Analysis', color="black", size="h1"), + dmc.Space(h=20), + dmc.Text("Insights into a Hinge User's Experiences", style={"fontSize": 24}, weight=500), + dmc.Text("This project analyzes personal data exported from Hinge to provide valuable insights into the user's " + "experiences on the platform. By examining the user's profile, dating preferences, and interactions with other users, " + "the project aims to reveal patterns, trends, and meaningful statistics that enhance the understanding of how users " + "engage with Hinge and make decisions based on their preferences."), + dmc.Space(h=20), - # informational info about the app - html.Div([ - dmc.Space(h=20), - dmc.Text("What This Is", style={"fontSize": 28}, weight=500), - dmc.Text("This application is meant to help provide meaningful insights about interactions users had with " - "people on the Hinge dating app."), - dmc.Space(h=20), - dmc.Text("Hinge allows users to request an export of their personal data that was " - "collected while they were using the app. If you have a Hinge account, you can request your data by going " - "to Settings -> Download My Data. It typically takes between 24 and 48 hours to fulfill this request, and " - "once the data are ready, Hinge provides a `.zip` file with your personal data."), - dmc.Space(h=20), - dmc.Text( - "The data export provided by Hinge contains several files, but the main thing is the `index.html` file, " - "which is used to render a webpage with tabs showing different data. The tabs provided by Hinge are " - "labeled: User, Matches, Prompts, Media, Subscriptions, Fresh Starts, and Selfie Verification. Aside from " - "viewing changes to your prompts or seeing which pictures you've uploaded, these data are not " - "particularly useful, especially the Matches tab, which is the most disappointing. The Matches tab " - "contains a list of `matches`, but I actually refer to them as `interactions` in this project because " - "not all of them are true matches- some are just unrequited likes or unmatches. Needless to say the export " - "provided by Hinge leaves a lot to be desired, so this project is meant to provide more insights."), - dmc.Space(h=20), - dmc.Text("How It Works", style={"fontSize": 28}, weight=500), - dmc.Text( - "After you get an email from Hinge saying your data export is complete, go to the app and download the " - "export. Navigate to where the export was downloaded and open the `.zip` file. From here you should see " - "the `matches.json` file and the `user.json` file which can be used for this analysis."), - dmc.Space(h=20), - dmc.Text("Caveats", size="xl"), - dmc.Text( - "1. Hinge does not provide any documentation about the data in the export so this analysis is based off my" - " own inferences from working with the data"), - dmc.Text( - "2. Hinge occasionally updates and modifies the data they send in the export, which may or may not make " - "aspects of this analysis obsolete or cause it to break"), - dmc.Space(h=20), - dmc.Text("Assumptions", size="xl"), - dmc.Text( - "Since there is no documentation provided by Hinge, here are some assumptions I am making about the data " - "in the export: "), - dmc.Text("1. Unmatches, or `blocks` as Hinge refers to them, could go either direction, meaning you " - "could have unmatched the other person or they could have unmatched you. Hinge does not include any " - "additional data in these events to tell who unmatched who"), - dmc.Text( - "2. Matches without a like in the same event mean that someone liked you first, and you chose to match " - "with them (i.e. they liked you first)"), - dmc.Space(h=30)]), - - # section for uploading files - # html.Div([ - # dmc.Text("Upload Files", style={"fontSize": 28}, weight=500), - # dmc.Text("Upload the `matches.json` and the `user.json` files from the zipped Hinge export for analysis."), - # dmc.Space(h=20), - # dcc.Upload( - # id='upload-data', - # children=html.Div([ - # 'Drag and Drop or ', - # html.A('Select Files') - # ]), - # style={ - # 'width': '100%', - # 'height': '60px', - # 'lineHeight': '60px', - # 'borderWidth': '1px', - # 'borderStyle': 'dashed', - # 'borderRadius': '5px', - # 'textAlign': 'center', - # 'margin': '10px', - # "fontSize": 20, - # 'font-family': "Open Sans, verdana, arial, sans-serif" - # }, - # # Allow multiple files to be uploaded - # multiple=True - # ), - # html.Div(id='output-data-upload') - # ]), - - # show links to the other pages - dmc.Text("Data Insights", style={"fontSize": 28}, weight=500), - # dmc.Text("After uploading your data files, you can click on the page links below to see insights " - # "from the data provided by Hinge."), + dmc.Text("Explore More About the App", style={"fontSize": 24}, weight=500), + dmc.Text("For a deeper dive into the data and insights, explore the following sections:"), dmc.Space(h=10), + # show links to the other pages html.Div([ html.Div( - dcc.Link(f"{page['name'].title()}", href=page["relative_path"], - style={"fontSize": 20, 'font-family': "Open Sans, verdana, arial, sans-serif"}) + [ + # Display the page name as a link + dcc.Link(f"{page['name'].title()}", href=page["relative_path"], + style={"fontSize": 20, 'font-family': "Open Sans, verdana, arial, sans-serif"}), + + # Add additional text based on the page name + html.P( + # Conditional text for each page + f"{get_additional_text(page['name'])}", + style={"fontSize": 14, 'font-family': "Open Sans, verdana, arial, sans-serif"} + ) if page["name"] != "Home" else None + ] ) for page in dash.page_registry.values() if page["name"] != "Home" ]), dmc.Space(h=20), @@ -126,34 +76,6 @@ ]) -def parse_uploaded_file_contents(list_of_file_contents, list_of_file_names): - if not os.path.exists(USER_FILE_UPLOAD_DIRECTORY): - logger.info(f"Creating the user file upload directory: {USER_FILE_UPLOAD_DIRECTORY}." ) - os.makedirs(USER_FILE_UPLOAD_DIRECTORY) - - for file_content, file_name in zip(list_of_file_contents, list_of_file_names): - uploaded_file_data = file_content.encode("utf8").split(b";base64,")[1] - - with open(os.path.join(USER_FILE_UPLOAD_DIRECTORY, file_name), "wb") as uploaded_file: - logger.info(f"Uploading user file: {file_name}...") - uploaded_file.write(base64.decodebytes(uploaded_file_data)) - - # return an html Div of the uploaded file names to display to the user - return html.Div([ - html.Div( - dmc.Text(file_name, style={"fontSize": 16, 'font-family': "Open Sans, verdana, arial, sans-serif"}) - ) for file_name in list_of_file_names - ]) - - -# @callback(Output('output-data-upload', 'children'), -# Input('upload-data', 'contents'), -# State('upload-data', 'filename')) -# def update_output(list_of_contents, list_of_names): -# if list_of_contents is not None: -# children = [ -# parse_uploaded_file_contents(list_of_contents, list_of_names)] -# return children if __name__ == '__main__': diff --git a/app/pages/InfoPage.py b/app/pages/InfoPage.py new file mode 100644 index 0000000..ba260da --- /dev/null +++ b/app/pages/InfoPage.py @@ -0,0 +1,50 @@ +from dash import html +import dash_mantine_components as dmc + +layout = html.Div([ + dmc.Space(h=10), + dmc.Text("What This Is", style={"fontSize": 28}, weight=500), + dmc.Text("This application is meant to help provide meaningful insights about interactions users had with " + "people on the Hinge dating app."), + dmc.Space(h=20), + dmc.Text("Hinge allows users to request an export of their personal data that was " + "collected while they were using the app. If you have a Hinge account, you can request your data by going " + "to Settings -> Download My Data. It typically takes between 24 and 48 hours to fulfill this request, and " + "once the data are ready, Hinge provides a `.zip` file with your personal data."), + dmc.Space(h=20), + dmc.Text( + "The data export provided by Hinge contains several files, but the main thing is the `index.html` file, " + "which is used to render a webpage with tabs showing different data. The tabs provided by Hinge are " + "labeled: User, Matches, Prompts, Media, Subscriptions, Fresh Starts, and Selfie Verification. Aside from " + "viewing changes to your prompts or seeing which pictures you've uploaded, these data are not " + "particularly useful, especially the Matches tab, which is the most disappointing. The Matches tab " + "contains a list of `matches`, but I actually refer to them as `interactions` in this project because " + "not all of them are true matches- some are just unrequited likes or unmatches. Needless to say the export " + "provided by Hinge leaves a lot to be desired, so this project is meant to provide more insights."), + dmc.Space(h=20), + dmc.Text("How It Works", style={"fontSize": 28}, weight=500), + dmc.Text( + "After you get an email from Hinge saying your data export is complete, go to the app and download the " + "export. Navigate to where the export was downloaded and open the `.zip` file. From here you should see " + "the `matches.json` file and the `user.json` file which can be used for this analysis."), + dmc.Space(h=20), + dmc.Text("Caveats", size="xl"), + dmc.Text( + "1. Hinge does not provide any documentation about the data in the export so this analysis is based off my" + " own inferences from working with the data"), + dmc.Text( + "2. Hinge occasionally updates and modifies the data they send in the export, which may or may not make " + "aspects of this analysis obsolete or cause it to break"), + dmc.Space(h=20), + dmc.Text("Assumptions", size="xl"), + dmc.Text( + "Since there is no documentation provided by Hinge, here are some assumptions I am making about the data " + "in the export: "), + dmc.Text("1. Unmatches, or `blocks` as Hinge refers to them, could go either direction, meaning you " + "could have unmatched the other person or they could have unmatched you. Hinge does not include any " + "additional data in these events to tell who unmatched who"), + dmc.Text( + "2. Matches without a like in the same event mean that someone liked you first, and you chose to match " + "with them (i.e. they liked you first)"), + dmc.Space(h=30) +]) \ No newline at end of file