Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

# CodeClocker Changelog

## 1.0.7 - 2025-04-16
### Added
- Show subscription expiration notification on every IDE restart

## 1.0.6 - 2025-04-08
### Added
- Improve onboarding UX

## 1.0.5 - 2025-04-04
### Added
- Add plugin icon
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pluginGroup = com.codeclocker
pluginName = CodeClocker
pluginRepositoryUrl = https://github.com/codeclocker/codeclocker-intellij-plugin
# SemVer format -> https://semver.org
pluginVersion = 1.0.5
pluginVersion = 1.0.7

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 242
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,22 @@

import com.intellij.ide.BrowserUtil;
import com.intellij.ide.DataManager;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationAction;
import com.intellij.notification.NotificationGroupManager;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import java.util.concurrent.atomic.AtomicBoolean;

public class Notifications {
public class NotificationService {

private static final Logger LOG = Logger.getInstance(Notifications.class);
public static final AtomicBoolean SUBSCRIPTION_WILL_EXPIRE_SOON_NOTIFICATION_SHOWN =
new AtomicBoolean();
public static final AtomicBoolean SUBSCRIPTION_EXPIRED_NOTIFICATION_SHOWN = new AtomicBoolean();

public static final String SUBSCRIPTION_WILL_EXPIRE_SOON_NOTIFICATION_SHOWN =
"com.codeclocker.subscription-will-expire-soon-notification-shown";
public static final String SUBSCRIPTION_EXPIRED_NOTIFICATION_SHOWN =
"com.codeclocker.subscription-expired-notification-shown";

public static void showInvalidApiKeyNotification() {
public void showInvalidApiKeyNotification() {
Notification notification =
NotificationGroupManager.getInstance()
.getNotificationGroup("CodeClocker")
Expand All @@ -37,8 +33,8 @@ public static void showInvalidApiKeyNotification() {
ApplicationManager.getApplication().invokeLater(() -> notification.notify(getCurrentProject()));
}

public static void showSubscriptionWillExpireSoonNotification() { // todo: decide how to reset
if (checkAndSet(SUBSCRIPTION_WILL_EXPIRE_SOON_NOTIFICATION_SHOWN)) {
public void showSubscriptionWillExpireSoonNotification() {
if (SUBSCRIPTION_WILL_EXPIRE_SOON_NOTIFICATION_SHOWN.getAndSet(true)) {
return;
}

Expand All @@ -47,17 +43,17 @@ public static void showSubscriptionWillExpireSoonNotification() { // todo: decid
.getNotificationGroup("CodeClocker")
.createNotification(
"CodeClocker subscription will expire soon",
"Your CodeClocker subscription will expire soon. Renew now to continue tracking activity without interruptions.",
"Your CodeClocker subscription will expire soon. Renew it now to keep tracking activity.",
WARNING)
.addAction(
NotificationAction.createSimpleExpiring(
"Renew now", () -> BrowserUtil.browse(HUB_UI_HOST + "/api-key")));
"Renew now", () -> BrowserUtil.browse(HUB_UI_HOST + "/payment")));

ApplicationManager.getApplication().invokeLater(() -> notification.notify(getCurrentProject()));
}

public static void showPaymentExpiredNotification() {
if (checkAndSet(SUBSCRIPTION_EXPIRED_NOTIFICATION_SHOWN)) {
public void showSubscriptionExpiredNotification() {
if (SUBSCRIPTION_EXPIRED_NOTIFICATION_SHOWN.getAndSet(true)) {
return;
}

Expand All @@ -66,11 +62,11 @@ public static void showPaymentExpiredNotification() {
.getNotificationGroup("CodeClocker")
.createNotification(
"CodeClocker subscription expired",
"Your CodeClocker subscription expired. Renew now to keep tracking activity.",
"Your CodeClocker subscription expired. Renew it now to keep tracking activity.",
WARNING)
.addAction(
NotificationAction.createSimpleExpiring(
"Renew now", () -> BrowserUtil.browse(HUB_UI_HOST + "/api-key")));
"Renew now", () -> BrowserUtil.browse(HUB_UI_HOST + "/payment")));

ApplicationManager.getApplication().invokeLater(() -> notification.notify(getCurrentProject()));
}
Expand All @@ -79,15 +75,4 @@ private static Project getCurrentProject() {
DataContext dataContext = DataManager.getInstance().getDataContext(null);
return dataContext.getData(CommonDataKeys.PROJECT);
}

private static boolean checkAndSet(String property) {
PropertiesComponent properties = PropertiesComponent.getInstance();
boolean shown = properties.getBoolean(property, false);
LOG.debug("Shown [{}] by property [{}]", shown, property);
if (!shown) {
properties.setValue(property, true);
}

return shown;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.codeclocker.plugin.intellij;

import java.util.concurrent.Executors;
import com.intellij.util.concurrency.AppExecutorUtil;
import java.util.concurrent.ScheduledExecutorService;

public class ScheduledExecutor {

public static final ScheduledExecutorService EXECUTOR =
Executors.newSingleThreadScheduledExecutor();
AppExecutorUtil.getAppScheduledExecutorService();
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.codeclocker.plugin.intellij.apikey;

import static com.codeclocker.plugin.intellij.Notifications.SUBSCRIPTION_EXPIRED_NOTIFICATION_SHOWN;
import static com.codeclocker.plugin.intellij.NotificationService.SUBSCRIPTION_EXPIRED_NOTIFICATION_SHOWN;
import static org.apache.commons.lang3.StringUtils.isBlank;

import com.codeclocker.plugin.intellij.Notifications;
import com.codeclocker.plugin.intellij.NotificationService;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.Nullable;
Expand All @@ -16,21 +17,29 @@ public class ApiKeyLifecycle {
public static final String ACTIVITY_DATA_STOPPED_BEING_COLLECTED_PROPERTY =
"com.codeclocker.activity-data-stopped-being-collected";

private final NotificationService notificationService;

public ApiKeyLifecycle() {
this.notificationService =
ApplicationManager.getApplication().getService(NotificationService.class);
}

public void processHubErrorResponse(@Nullable String response) {
if (response == null) {
return;
}

if (StringUtils.contains(response, "Unknown API key")) {
ApiKeyPersistence.unsetApiKey();
Notifications.showInvalidApiKeyNotification();
notificationService.showInvalidApiKeyNotification();
} else if (StringUtils.contains(response, "Subscription expire soon")) {
// todo: show notification
notificationService.showSubscriptionWillExpireSoonNotification();
} else if (StringUtils.contains(response, "Subscription expired")) {
Notifications.showPaymentExpiredNotification();
notificationService.showSubscriptionExpiredNotification();
} else if (StringUtils.contains(response, "Activity data stopped being collected")) {
PropertiesComponent.getInstance()
.setValue(ACTIVITY_DATA_STOPPED_BEING_COLLECTED_PROPERTY, true);
notificationService.showSubscriptionExpiredNotification();
}
}

Expand All @@ -43,7 +52,7 @@ public static void continueCollectingActivityData() {
LOG.debug("Continuing collecting data by API Key");
PropertiesComponent properties = PropertiesComponent.getInstance();
properties.setValue(ACTIVITY_DATA_STOPPED_BEING_COLLECTED_PROPERTY, false);
properties.setValue(SUBSCRIPTION_EXPIRED_NOTIFICATION_SHOWN, false);
SUBSCRIPTION_EXPIRED_NOTIFICATION_SHOWN.set(false);
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,13 @@ public void actionPerformed(@NotNull AnActionEvent e) {
public static void showAction() {
String text = getText();
int result =
Messages.showYesNoCancelDialog(
Messages.showOkCancelDialog(
text,
"Enter CodeClocker API Key",
"Get API Key",
"I Have API Key",
"Cancel",
Messages.getInformationIcon());

if (result == Messages.NO) {
showApiKeyInputDialog();
} else if (result == Messages.YES) {
if (result == Messages.YES) {
BrowserUtil.browse(HUB_UI_HOST + "/api-key");
showApiKeyInputDialog();
}
Expand All @@ -41,20 +37,20 @@ private static String getText() {
String apiKey = ApiKeyPersistence.getApiKey();
if (isBlank(apiKey)) {
return """
Need an API key? Click 'Get API Key' to get one.""";
Enter your CodeClocker API Key to start tracking your coding activity.""";
}

return """
Your current API key: %s

Need an API key? Click 'Get API Key' to get one."""
Enter your CodeClocker API Key to start tracking your coding activity."""
.formatted(apiKey);
}

private static void showApiKeyInputDialog() {
String apiKey =
Messages.showInputDialog(
"Enter your API key\n\nCopy from: hub.codeclocker.com/api-key",
"Enter your API key\n\nCopy it from: hub.codeclocker.com/api-key",
"Activate CodeClocker",
Messages.getInformationIcon(),
null,
Expand Down
1 change: 1 addition & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.reporting.ActivitySampleHttpClient"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.apikey.ApiKeyLifecycle"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.config.ConfigProvider"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.NotificationService"/>
<applicationService serviceImplementation="com.codeclocker.plugin.intellij.config.PluginConfigHttpClient"/>
<applicationService
serviceImplementation="com.codeclocker.plugin.intellij.subscription.SubscriptionStateCheckerTask"/>
Expand Down
Loading