Skip to content

Commit

Permalink
Merge branch 'release/2.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Stefano Tranquillini committed Feb 17, 2025
2 parents d5e86a1 + eae1cb4 commit 19f178c
Show file tree
Hide file tree
Showing 10 changed files with 714 additions and 214 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,5 @@ cython_debug/
# avoiding crendtials and token.. for time being
src/token.json
src/*.json
.vscode
.vscode
src/Vince.zip
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
# Vince

Vince - Early is on time, on time is late and late is unacceptable!

www.stefanotranquillini.com/vince

## Main features

![img](https://github.com/esseti/vince/assets/1928354/a78b9221-67de-4e5a-a13d-e0a874ec5237)

- Today's Meeting Showcase:
Vince conveniently displays your current events in the menu bar. It highlights events that are currently happening, accompanied by a dynamic countdown showing the time remaining in the format HH:MM:SS.
Vince conveniently displays your current events in the menu bar. It highlights events that are currently happening, accompanied by a dynamic countdown showing the time remaining in the format HH:MM:SS.

- Next Meeting Countdown:
Stay one step ahead with Vince's countdown timer for your next meeting. It shows the time left until your next event, ensuring you're prepared and punctual in the format HH:MM.
Stay one step ahead with Vince's countdown timer for your next meeting. It shows the time left until your next event, ensuring you're prepared and punctual in the format HH:MM.

- Real-Time Meeting Progress:
Stay engaged and in control during your current meeting. Vince keeps you informed about the exact time remaining in your ongoing meeting, allowing you to contribute confidently and manage your time efficiently.
Stay engaged and in control during your current meeting. Vince keeps you informed about the exact time remaining in your ongoing meeting, allowing you to contribute confidently and manage your time efficiently.

- Comprehensive Meeting Overview:
Vince offers a comprehensive drop-down menu with a complete overview of all your scheduled meetings for the day. Each entry includes the start and end time of the meeting, along with the time left before it begins.
Vince offers a comprehensive drop-down menu with a complete overview of all your scheduled meetings for the day. Each entry includes the start and end time of the meeting, along with the time left before it begins.

- One-Click Meeting Access:
If your events include a meeting link, Vince makes it effortless to join. Simply click on the event, and Vince will automatically open your preferred web browser, ensuring you never miss an important meeting.
If your events include a meeting link, Vince makes it effortless to join. Simply click on the event, and Vince will automatically open your preferred web browser, ensuring you never miss an important meeting.

- Never miss a call:
When a meeting includes a Meet link for a video call, Vince takes it a step further. One minute before the start of the event, Vince automatically opens the Meet URL, ensuring you effortlessly join the meeting on time and without any delays.
When a meeting includes a Meet link for a video call, Vince takes it a step further. One minute before the start of the event, Vince automatically opens the Meet URL, ensuring you effortlessly join the meeting on time and without any delays.

- Smart Meeting Notifications:
Vince provides timely notifications to keep you informed. It sends a notification one minute before a meeting starts, giving you a gentle reminder to prepare. Additionally, Vince notifies you five minutes before a meeting ends, ensuring a smooth transition to your next task.
Vince provides timely notifications to keep you informed. It sends a notification one minute before a meeting starts, giving you a gentle reminder to prepare. Additionally, Vince notifies you five minutes before a meeting ends, ensuring a smooth transition to your next task.

- Meeting Completion Alerts:
When a meeting's scheduled time is over, Vince sends a notification, ensuring you stay on top of your schedule and can efficiently manage your time.
When a meeting's scheduled time is over, Vince sends a notification, ensuring you stay on top of your schedule and can efficiently manage your time.

- Seamless Google Calendar Integration:
Vince seamlessly connects to your Google Calendar, providing real-time access to your events and appointments. Stay updated and never miss a beat.
Vince seamlessly connects to your Google Calendar, providing real-time access to your events and appointments. Stay updated and never miss a beat.

#### NOTE

The name in honor of vince lombardi and his attitute on being on time.
Still, the quote is from "Eric Jerome Dickey."
Still, the quote is from "Eric Jerome Dickey."
16 changes: 16 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
https://github.com/jaredks/rumps/issues/200#issuecomment-1475380684
[2.0.0] 2025-02-17

- Adding popup that shows a timer. It's shown +1:00 before the meeting starts in pink, then green until 5:00 minutes left to the end of the meeting, then yellow, orange and red whe time is over. If you close the popup you can re-open it from the menu bar (only if the meeting is still going on).

[1.2.0] 2024-06-05

- Added support for chrome meet app to open the meet app link (pedro request).
To enable this feature, follow these steps:

1. Locate the Google Meet app on your system. It's typically found at this path: `/Users/YOUR_USER/Applications/Chrome\ Apps.localized/Google\ Meet.app`.
2. Copy this path and replace any single backslash `\` with a double backslash `\\`. The modified path should look like this: `/Users/YOUR_USER/Applications/Chrome\\ Apps.localized/Google\\ Meet.app`.
3. Paste this modified path into the settings under the `app_meet` field.
4. Once done, any Google Meet link will trigger the app to open.

Please note a known limitation: The app does not automatically open the meeting link. You will need to manually enter the meeting ID or select the meeting from your event list on the right side of the app.
32 changes: 32 additions & 0 deletions src/_setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""
This is a setup.py script generated by py2applet
Usage:
python setup.py py2app
"""

from setuptools import setup

APP = ['vince.py']
DATA_FILES = [('', ['credentials.json','icon.png','menu-icon.png'])]

OPTIONS = {
'argv_emulation': False,
'plist': {'LSUIElement': True,
'CFBundleName': 'Vince2',
'CFBundleShortVersionString': '1.2.0',
},
'iconfile':'icon.png',
}


setup(
app=APP,
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
author='Stefano Tranquillini', # Set the author name here
author_email='[email protected]', # Set the author email here
url='https://www.stefanotranquillini.com', # Set the project URL here
license='GNU GPL 3', # Set the project license here
)
2 changes: 1 addition & 1 deletion src/build.fish
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
rm -rf dist/
rm -rf build/
python setup.py py2app
python setup.py py2app --includes imp
216 changes: 216 additions & 0 deletions src/countdown_window.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
import AppKit
import time
from datetime import datetime, timedelta
from Foundation import NSMakeRect, NSMakePoint
import pytz

import logging


class CountdownWindowDelegate(AppKit.NSObject):
def windowWillClose_(self, notification):
window = notification.object()
if hasattr(window, "owner"):
window.owner.handle_window_closed()


class CountdownWindow:
def __init__(self, event, parent=None):
self.parent = parent
self.event = event
# Create window delegate
self.delegate = CountdownWindowDelegate.alloc().init()

self.window = AppKit.NSWindow.alloc()
screen = AppKit.NSScreen.mainScreen()
screen_frame = screen.frame()
window_width = 200.0
window_height = 50.0
frame = (
(
screen_frame.size.width / 2 - window_width / 2,
screen_frame.size.height - window_height,
),
(window_width, window_height),
)
self.window.initWithContentRect_styleMask_backing_defer_(
frame,
AppKit.NSWindowStyleMaskBorderless, # Remove title bar
AppKit.NSBackingStoreBuffered,
False,
)
# Make window movable by dragging anywhere
self.window.setMovableByWindowBackground_(True)

self.paretn = parent

# Set window properties for transparency and rounded corners
self.window.setBackgroundColor_(AppKit.NSColor.clearColor())
self.window.setOpaque_(False)

# Create a visual effect view for the background
visual_effect = AppKit.NSVisualEffectView.alloc().initWithFrame_(
self.window.contentView().bounds()
)
visual_effect.setAutoresizingMask_(
AppKit.NSViewWidthSizable | AppKit.NSViewHeightSizable
)
visual_effect.setWantsLayer_(True)
visual_effect.layer().setCornerRadius_(10.0) # Set corner radius
visual_effect.layer().setMasksToBounds_(True)

# Set the visual effect to be light and transparent
visual_effect.setMaterial_(AppKit.NSVisualEffectMaterialLight)
visual_effect.setBlendingMode_(AppKit.NSVisualEffectBlendingModeWithinWindow)
visual_effect.setState_(AppKit.NSVisualEffectStateActive)

# Replace the content view with our visual effect view
self.window.contentView().addSubview_(visual_effect)
self.visual_effect = visual_effect

# Create and configure the countdown label
content_view = self.visual_effect
# Make the label height bigger to accommodate the font size
label_height = 30
label_width = window_width - 40 # Leave space for close button
self.label = AppKit.NSTextField.alloc().initWithFrame_(
NSMakeRect(
10, (window_height - label_height) / 2, label_width, label_height
)
)
self.label.setBezeled_(False)
self.label.setDrawsBackground_(False)
self.label.setEditable_(False)
self.label.setSelectable_(False)
self.label.setAlignment_(AppKit.NSTextAlignmentCenter)
self.label.setFont_(AppKit.NSFont.boldSystemFontOfSize_(24))
self.label.setTextColor_(AppKit.NSColor.blackColor())
content_view.addSubview_(self.label)

# Create and configure the event name label
event_name_label_height = 20
event_name_label = AppKit.NSTextField.alloc().initWithFrame_(
NSMakeRect(10, -5, label_width, event_name_label_height)
)
event_name_label.setBezeled_(False)
event_name_label.setDrawsBackground_(False)
event_name_label.setEditable_(False)
event_name_label.setSelectable_(False)
event_name_label.setAlignment_(AppKit.NSTextAlignmentCenter)
event_name_label.setFont_(AppKit.NSFont.systemFontOfSize_(10))
event_name_label.setTextColor_(AppKit.NSColor.blackColor())
event_name_label.setStringValue_(self.event["summary"])
content_view.addSubview_(event_name_label)

# Add close button
button_size = 20
close_button = AppKit.NSButton.alloc().initWithFrame_(
NSMakeRect(
window_width - button_size - 10,
(window_height - button_size) / 2,
button_size,
button_size,
)
)
close_button.setBezelStyle_(AppKit.NSBezelStyleCircular)
close_button.setTitle_("×")
# close_button.setFontColor_(AppKit.NSColor.blackColor())
close_button.setTarget_(self)
close_button.setAction_("close")
attributes = {
AppKit.NSForegroundColorAttributeName: AppKit.NSColor.blackColor(),
AppKit.NSFontAttributeName: AppKit.NSFont.boldSystemFontOfSize_(16),
}
attributed_title = AppKit.NSAttributedString.alloc().initWithString_attributes_(
"×", attributes
)

# Apply the styled title
close_button.setAttributedTitle_(attributed_title)

content_view.addSubview_(close_button)

# Set initial background color (yellow)
initial_color = AppKit.NSColor.colorWithRed_green_blue_alpha_(
0.0, 0.0, 0.0, 0.95
) # Bright yellow
self.visual_effect.layer().setBackgroundColor_(initial_color.CGColor())

self.window.makeKeyAndOrderFront_(None)
self.window.setLevel_(AppKit.NSFloatingWindowLevel)
self.timer = None

def start_countdown(self):
self.timer = AppKit.NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(
1.0,
self,
"timerCallback:",
{
"end_time": self.event["end"],
"start_time": self.event["start"],
},
True,
)
AppKit.NSRunLoop.currentRunLoop().addTimer_forMode_(
self.timer, AppKit.NSRunLoopCommonModes
)

def timerCallback_(self, timer):
start_time = timer.userInfo()["start_time"]
end_time = timer.userInfo()["end_time"]
time_to_use = start_time if start_time > datetime.now(pytz.utc) else end_time
sign = None
if time_to_use == start_time:
sign = "+"
now = datetime.now(pytz.utc)
time_diff = time_to_use - now

logging.debug(
f"Time diff: {time_diff}, sign: {sign} time_to_use: {time_to_use}, now: {now}"
)

total_seconds = int(time_diff.total_seconds())
minutes = abs(total_seconds) // 60
seconds = abs(total_seconds) % 60

# Update background color based on time
if sign == "+":
color = AppKit.NSColor.colorWithRed_green_blue_alpha_(
155 / 255, 89 / 255, 182 / 255, 0.95
) # purple
elif total_seconds < 0:
# Red for expired
color = AppKit.NSColor.colorWithRed_green_blue_alpha_(
231 / 255, 76 / 255, 60 / 255, 1.0
) # Bright red
elif total_seconds <= 300 and total_seconds > 60: # Between 1 and 5 minutes
color = AppKit.NSColor.colorWithRed_green_blue_alpha_(
241 / 255, 196 / 255, 15 / 255, 1.0
) # Yellow
elif total_seconds <= 60 and total_seconds > 0: # Less than 1 minute
color = AppKit.NSColor.colorWithRed_green_blue_alpha_(
230 / 255, 126 / 255, 34 / 255, 1.0
) # Orange
else: # More than 5 minutes
color = AppKit.NSColor.colorWithRed_green_blue_alpha_(
46 / 255, 204 / 255, 113 / 255, 1.0
) # Green

if not sign:
sign = "-" if total_seconds < 0 else ""
countdown_text = f"{sign}{minutes:01d}:{seconds:02d}"
self.label.setStringValue_(countdown_text)

self.visual_effect.layer().setBackgroundColor_(color.CGColor())

def show(self):
# Show the window
self.window.makeKeyAndOrderFront_(self.window)

def close(self):
if self.timer:
self.timer.invalidate()
self.window.close()
if self.parent and self.event and "id" in self.event:
if self.event["id"] in self.parent.countdown_windows:
self.parent.countdown_windows[self.event["id"]]["closed"] = True
3 changes: 3 additions & 0 deletions src/dist.fish
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
build.fish
cd dist && zip -r ../VInce.zip VInce.app
7 changes: 5 additions & 2 deletions src/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ google-auth-httplib2
google-auth-oauthlib
pytz
py2app
py2app[setuptools]
# py2app[setuptools]
requests
chardet
appdirs
requests
bs4
bs4
setuptools<71
#AppKit
PyObjC-core
32 changes: 16 additions & 16 deletions src/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,26 @@

from setuptools import setup

APP = ['vince.py']
DATA_FILES = [('', ['credentials.json','icon.png','menu-icon.png'])]
APP = ["vince.py"]
DATA_FILES = [("", ["credentials.json", "icon.png", "menu-icon.png"])]

OPTIONS = {
'argv_emulation': False,
'plist': {'LSUIElement': True,
'CFBundleName': 'Vince',
'CFBundleShortVersionString': '1.2.0',
},
'iconfile':'icon.png',
}

"argv_emulation": False,
"plist": {
"LSUIElement": True,
"CFBundleName": "Vince",
"CFBundleShortVersionString": "2.0",
},
"iconfile": "icon.png",
}

setup(
app=APP,
data_files=DATA_FILES,
options={'py2app': OPTIONS},
setup_requires=['py2app'],
author='Stefano Tranquillini', # Set the author name here
author_email='[email protected]', # Set the author email here
url='https://www.stefanotranquillini.com', # Set the project URL here
license='GNU GPL 3', # Set the project license here
options={"py2app": OPTIONS},
setup_requires=["py2app"],
author="Stefano Tranquillini", # Set the author name here
author_email="[email protected]", # Set the author email here
url="https://www.stefanotranquillini.com", # Set the project URL here
license="GNU GPL 3", # Set the project license here
)
Loading

0 comments on commit 19f178c

Please sign in to comment.