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 @@ +