diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3c2d0f9..01fb912 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/gradle.properties b/gradle.properties
index 6bf4e77..69ef0d3 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -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
diff --git a/src/main/java/com/codeclocker/plugin/intellij/Notifications.java b/src/main/java/com/codeclocker/plugin/intellij/NotificationService.java
similarity index 63%
rename from src/main/java/com/codeclocker/plugin/intellij/Notifications.java
rename to src/main/java/com/codeclocker/plugin/intellij/NotificationService.java
index 4ecb702..bc7ab85 100644
--- a/src/main/java/com/codeclocker/plugin/intellij/Notifications.java
+++ b/src/main/java/com/codeclocker/plugin/intellij/NotificationService.java
@@ -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")
@@ -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;
}
@@ -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;
}
@@ -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()));
}
@@ -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;
- }
}
diff --git a/src/main/java/com/codeclocker/plugin/intellij/ScheduledExecutor.java b/src/main/java/com/codeclocker/plugin/intellij/ScheduledExecutor.java
index 0adb41d..deb9a55 100644
--- a/src/main/java/com/codeclocker/plugin/intellij/ScheduledExecutor.java
+++ b/src/main/java/com/codeclocker/plugin/intellij/ScheduledExecutor.java
@@ -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();
}
diff --git a/src/main/java/com/codeclocker/plugin/intellij/apikey/ApiKeyLifecycle.java b/src/main/java/com/codeclocker/plugin/intellij/apikey/ApiKeyLifecycle.java
index 972e7da..4f8659a 100644
--- a/src/main/java/com/codeclocker/plugin/intellij/apikey/ApiKeyLifecycle.java
+++ b/src/main/java/com/codeclocker/plugin/intellij/apikey/ApiKeyLifecycle.java
@@ -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;
@@ -16,6 +17,13 @@ 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;
@@ -23,14 +31,15 @@ public void processHubErrorResponse(@Nullable String response) {
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();
}
}
@@ -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
diff --git a/src/main/java/com/codeclocker/plugin/intellij/apikey/EnterApiKeyAction.java b/src/main/java/com/codeclocker/plugin/intellij/apikey/EnterApiKeyAction.java
index 298ba60..4e35d98 100644
--- a/src/main/java/com/codeclocker/plugin/intellij/apikey/EnterApiKeyAction.java
+++ b/src/main/java/com/codeclocker/plugin/intellij/apikey/EnterApiKeyAction.java
@@ -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();
}
@@ -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,
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 70a38cf..8adad6e 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -19,6 +19,7 @@
+