Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
4ecb7fe
UI for reminder. Initial checks for reminderAt value.
rafaelfelipeac Apr 25, 2025
6ff5fa7
Improvements on Date and Time pickers.
rafaelfelipeac Apr 26, 2025
5f7f495
Improvements on Date and Time pickers.
rafaelfelipeac Apr 26, 2025
4c7b8f3
Improvements on Date and Time pickers.
rafaelfelipeac Apr 26, 2025
6457d89
Improvements on Date and Time pickers.
rafaelfelipeac Apr 26, 2025
77ed82c
Improvements on Date and Time pickers.
rafaelfelipeac Apr 26, 2025
d4ec264
Moving ReplyReminder to Common.
rafaelfelipeac Apr 26, 2025
2063f04
Renaming ReplyReminder.
rafaelfelipeac Apr 29, 2025
d3c6793
Added dismiss calls to datetime pickers.
rafaelfelipeac Apr 29, 2025
c101856
Fixing datetime issues.
rafaelfelipeac Apr 29, 2025
fc5f778
Fixing datetime issues.
rafaelfelipeac Apr 29, 2025
0836ed0
Reading reminderAt.
rafaelfelipeac Apr 29, 2025
66f397d
Database migration for reminderAt.
rafaelfelipeac Apr 30, 2025
645e97a
Added ReminderScheduler.
rafaelfelipeac May 6, 2025
9076842
Added ReminderScheduler.
rafaelfelipeac May 6, 2025
ccadfa4
Dealing with NotificationPermissionManager.
rafaelfelipeac May 6, 2025
27c6f95
Improvements on notificationPermissionManager.
rafaelfelipeac May 6, 2025
4e2d940
Improvements.
rafaelfelipeac May 7, 2025
25bc309
Improvements.
rafaelfelipeac May 7, 2025
1e2d9e8
Improvements.
rafaelfelipeac May 8, 2025
35bb40f
Added translated strings to Time and Date pickers.
rafaelfelipeac May 13, 2025
0ab9dc6
Method to open app settings.
rafaelfelipeac May 13, 2025
eb39d6c
Removing unused code in ensureNotificationPermission.
rafaelfelipeac May 13, 2025
64f892b
Improving ensureNotificationPermission uses.
rafaelfelipeac Jun 21, 2025
58ccf43
Improving ensureNotificationPermission uses.
rafaelfelipeac Jun 23, 2025
f81c578
Improving ensureNotificationPermission uses.
rafaelfelipeac Jun 23, 2025
8d28da0
Improvements.
rafaelfelipeac Jun 23, 2025
8b124a6
Improvements.
rafaelfelipeac Jun 23, 2025
69faa9b
Improvements.
rafaelfelipeac Jun 23, 2025
f3ef69a
Improvements.
rafaelfelipeac Jun 23, 2025
3b42433
Improvements.
rafaelfelipeac Jun 23, 2025
970305f
Fixed and improvements.
rafaelfelipeac Jun 25, 2025
529cb09
Datetime, packages and Modifiers improvements.
rafaelfelipeac Jun 26, 2025
40893b8
Package fixes.
rafaelfelipeac Jun 27, 2025
23de72a
Minor fixes.
rafaelfelipeac Jun 29, 2025
17829e3
Added PendingIntent to notifications.
rafaelfelipeac Jul 2, 2025
ea8b5ec
Minor improvements.
rafaelfelipeac Jul 2, 2025
9a40b47
Notification content improvements.
rafaelfelipeac Jul 3, 2025
3a23a46
Improvements in notifications.
rafaelfelipeac Jul 3, 2025
a1a93fb
Removing comment.
rafaelfelipeac Jul 3, 2025
61eb437
Fix unit tests.
rafaelfelipeac Jul 4, 2025
82d9746
Added support to reminders and notifications on ActivityLogScreen.
rafaelfelipeac Jul 4, 2025
75cb6ee
Removing Clock object and adding invalid datetime snackbar.
rafaelfelipeac Jul 22, 2025
93de3ac
Added replyListReminderInvalidDateTime message.
rafaelfelipeac Jul 22, 2025
ca9c8ba
Fixed unit tests.
rafaelfelipeac Jul 22, 2025
41f387e
Added replyListReminderSetSeparator message.
rafaelfelipeac Jul 22, 2025
3922680
Improvements.
rafaelfelipeac Jul 22, 2025
9f5fe98
Adding padding top for ReminderText.
rafaelfelipeac Jul 23, 2025
eea964f
Fixed import.
rafaelfelipeac Jul 23, 2025
842bfeb
Ktlint fixes.
rafaelfelipeac Jul 23, 2025
a8e2e9d
Detekt fixes.
rafaelfelipeac Jul 23, 2025
9059fb6
Ktlint fixes.
rafaelfelipeac Jul 23, 2025
55ec8e9
README update.
rafaelfelipeac Jul 24, 2025
18e0081
Bump version to 1.5.0.
rafaelfelipeac Jul 24, 2025
aedb80c
PR fixes.
rafaelfelipeac Jul 24, 2025
1e8b7af
Detekt fixes.
rafaelfelipeac Jul 24, 2025
b1087e3
PR fixes.
rafaelfelipeac Jul 28, 2025
916cee4
PR fixes.
rafaelfelipeac Jul 28, 2025
aa28804
PR fixes.
rafaelfelipeac Jul 28, 2025
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
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

---

## [v1.5.0] - 2025-07-24

### Added
- Reminders with custom date and time.
- Notifications for reminders, with permission handling.
- Reminder UI components (picker, chips, dialogs).
- Delete confirmation modal.
- Timestamps on replies.
- Snackbar for actions.

### Changed
- Improved UI and navigation from notifications.

### Fixed
- Timestamp defaults and date/time validation.

### Refactored
- Simplified state and intent handling.
- Code cleanup and test updates.

## [v1.4.0] - 2025-04-18

### Added
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ This app exists for anyone who's ever said, “Oops, I meant to reply to that!
With Reply Radar, your forgotten replies are finally on the radar.
A gentle nudge. A little order. A friendlier inbox — on your terms.


<a href="https://play.google.com/store/apps/details?id=com.rafaelfelipeac.replyradar">
<img
alt="Get it on Google Play"
Expand Down Expand Up @@ -42,11 +41,12 @@ It’s a playground, but also a showcase of how Kotlin can unify experiences acr
- **Active** – Messages still pending a reply
- **Resolved** – Messages you've replied to and marked as done
- **Archived** – Soft delete feature to hide messages you no longer want to see
- Set **reminders** to follow up on specific replies and get notified at the right time
- Clean and focused UI to help you stay on top of conversations
- Light **gamification** through action history (laying the groundwork for future features)
- **Offline-first experience** – no account, no server

> ⚠️ **Reminders (and other cool features) aren't here yet** — but they're definitely on the radar. Stay tuned!
> ⚠️ Some features are still brewing — but they're definitely on the radar. Stay tuned!

---

Expand Down Expand Up @@ -91,6 +91,12 @@ This is still a work-in-progress — a personal playground to explore architectu

---

## 🧠 A playground for Android, KMP, and AI-enhanced development

Besides being a way to dive deeper into Android and Kotlin Multiplatform, Reply Radar is also a personal sandbox to explore how **AI-assisted workflows** can support development — from ideation and architecture to automation and iteration. The goal is to build better, faster and with curiosity at the center of the process.

---

## 🚫 Contributing

Currently a solo mission, but who knows? Contributions might be welcome in the future.
Expand Down
5 changes: 3 additions & 2 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ android {
applicationId = "com.rafaelfelipeac.replyradar"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 14
versionName = "1.4.0"
versionCode = 15
versionName = "1.5.0"

manifestPlaceholders["appName"] = "@string/app_name"
}
Expand Down Expand Up @@ -216,6 +216,7 @@ tasks.register<JavaExec>("detektFormat") {

dependencies {
implementation(libs.androidx.ui.android)
implementation(libs.androidx.work.runtime.ktx)
debugImplementation(compose.uiTooling)
add("kspAndroid", libs.androidx.room.compiler)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "6fc3470e59a8628f2c278dc0df38ab28",
"identityHash": "5607ce6deeaf96cb63ae56e6e3177625",
"entities": [
{
"tableName": "replies",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `subject` TEXT NOT NULL, `isResolved` INTEGER NOT NULL, `isArchived` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `resolvedAt` INTEGER NOT NULL, `archivedAt` INTEGER NOT NULL)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `subject` TEXT NOT NULL, `isResolved` INTEGER NOT NULL, `isArchived` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `resolvedAt` INTEGER NOT NULL, `archivedAt` INTEGER NOT NULL, `reminderAt` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
Expand Down Expand Up @@ -61,6 +61,12 @@
"columnName": "archivedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "reminderAt",
"columnName": "reminderAt",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
Expand Down Expand Up @@ -113,7 +119,7 @@
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6fc3470e59a8628f2c278dc0df38ab28')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5607ce6deeaf96cb63ae56e6e3177625')"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "5607ce6deeaf96cb63ae56e6e3177625",
"entities": [
{
"tableName": "replies",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `subject` TEXT NOT NULL, `isResolved` INTEGER NOT NULL, `isArchived` INTEGER NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `resolvedAt` INTEGER NOT NULL, `archivedAt` INTEGER NOT NULL, `reminderAt` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subject",
"columnName": "subject",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isResolved",
"columnName": "isResolved",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isArchived",
"columnName": "isArchived",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "updatedAt",
"columnName": "updatedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "resolvedAt",
"columnName": "resolvedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "archivedAt",
"columnName": "archivedAt",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "reminderAt",
"columnName": "reminderAt",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
},
{
"tableName": "user_actions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `actionType` TEXT NOT NULL, `targetType` TEXT, `targetId` INTEGER, `createdAt` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "actionType",
"columnName": "actionType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "targetType",
"columnName": "targetType",
"affinity": "TEXT"
},
{
"fieldPath": "targetId",
"columnName": "targetId",
"affinity": "INTEGER"
},
{
"fieldPath": "createdAt",
"columnName": "createdAt",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5607ce6deeaf96cb63ae56e6e3177625')"
]
}
}
1 change: 1 addition & 0 deletions composeApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application
android:name=".ReplyRadarApplication"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,12 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color.Companion.Black
import androidx.compose.ui.tooling.preview.Preview
import com.rafaelfelipeac.replyradar.app.ReplyRadarApp
import com.rafaelfelipeac.replyradar.core.ConfigureSystemBars
import com.rafaelfelipeac.replyradar.core.notification.rememberNotificationPermissionManager

@Composable
@Preview
fun AndroidApp() {
fun AndroidApp(pendingReplyId: Long?) {
var isDark by remember { mutableStateOf(false) }
var backgroundColor by remember { mutableStateOf(Black) }

Expand All @@ -22,6 +21,8 @@ fun AndroidApp() {
onSystemBarsConfigured = { dark, bgColor ->
isDark = dark
backgroundColor = bgColor
}
},
notificationPermissionManager = rememberNotificationPermissionManager(),
pendingReplyId = pendingReplyId
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package com.rafaelfelipeac.replyradar
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import com.rafaelfelipeac.replyradar.core.util.AppConstants.INVALID_ID
import com.rafaelfelipeac.replyradar.core.util.AppConstants.PENDING_REPLY_ID_KEY

class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val pendingReplyId = intent?.getLongExtra(PENDING_REPLY_ID_KEY, INVALID_ID)

setContent {
AndroidApp()
AndroidApp(pendingReplyId = pendingReplyId)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.compose.ui.tooling.preview.Preview
import com.rafaelfelipeac.replyradar.features.reply.domain.model.Reply
import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListScreen
import com.rafaelfelipeac.replyradar.features.reply.presentation.replylist.ReplyListState
import kotlinx.coroutines.flow.flowOf

private val replies = (1L..10L).map {
Reply(
Expand All @@ -24,6 +25,7 @@ private fun ReplyListScreenPreview() {
),
onIntent = {},
onSettingsClick = {},
onActivityLogClick = {}
onActivityLogClick = {},
effect = flowOf()
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package com.rafaelfelipeac.replyradar

import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.os.Build
import com.rafaelfelipeac.replyradar.R.string.notification_channel_description
import com.rafaelfelipeac.replyradar.R.string.notification_channel_id
import com.rafaelfelipeac.replyradar.R.string.notification_channel_name
import com.rafaelfelipeac.replyradar.di.initKoin
import org.koin.android.ext.koin.androidContext

Expand All @@ -13,6 +20,23 @@ class ReplyRadarApplication : Application() {
initKoin {
androidContext(this@ReplyRadarApplication)
}

createNotificationChannel()
}

private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val id = getString(notification_channel_id)
val name = getString(notification_channel_name)
val descriptionText = getString(notification_channel_description)
val importance = IMPORTANCE_HIGH
val channel = NotificationChannel(id, name, importance).apply {
description = descriptionText
}
val notificationManager = getSystemService(NotificationManager::class.java)

notificationManager.createNotificationChannel(channel)
}
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,18 @@ import androidx.compose.runtime.SideEffect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import com.google.accompanist.systemuicontroller.rememberSystemUiController
import com.rafaelfelipeac.replyradar.core.util.setStatusBarColorCompat

@Composable
fun ConfigureSystemBars(darkTheme: Boolean, backgroundColor: Color) {
val activity = LocalContext.current as? Activity
val systemUiController = rememberSystemUiController()

SideEffect {
// Navigation Bar
systemUiController.setNavigationBarColor(
color = backgroundColor,
darkIcons = !darkTheme
)

// Status Bar
activity?.setStatusBarColorCompat(
color = backgroundColor,
useDarkIcons = !darkTheme
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.rafaelfelipeac.replyradar.core.util
package com.rafaelfelipeac.replyradar.core

import android.app.Activity
import androidx.compose.ui.graphics.Color
Expand Down
Loading