Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c1e18aa
feat: Implement splash screen
dev-ali-mansour Nov 17, 2025
85b7523
docs: Add screenshots to README
dev-ali-mansour Nov 17, 2025
07cda35
docs: Update "Getting Started" guide with CI/CD instructions
dev-ali-mansour Nov 17, 2025
afd6620
docs: Update architecture description in README
dev-ali-mansour Nov 17, 2025
31cc501
feat: Add codelab source and deployment workflow
dev-ali-mansour Nov 18, 2025
c90afa1
docs: Update codelab metadata and section duration
dev-ali-mansour Nov 18, 2025
689466a
fix: Remove divider from codelab frontmatter
dev-ali-mansour Nov 18, 2025
ac0054a
fix(codelab): Move images folder inside codelab directory
dev-ali-mansour Nov 18, 2025
e560626
docs: Fix image paths in codelab
dev-ali-mansour Nov 18, 2025
76de908
ci: Add URL prefix when exporting codelab
dev-ali-mansour Nov 18, 2025
af3ef9d
Revert "docs: Fix image paths in codelab"
dev-ali-mansour Nov 18, 2025
3de95fa
Reapply "docs: Fix image paths in codelab"
dev-ali-mansour Nov 18, 2025
234e0fb
docs: Set `home` property in codelab metadata
dev-ali-mansour Nov 18, 2025
8b0071e
docs: Update home URL to absolute path
dev-ali-mansour Nov 18, 2025
3de8692
docs: Update codelab metadata format
dev-ali-mansour Nov 18, 2025
3c1c749
docs: Add metadata to codelab
dev-ali-mansour Nov 18, 2025
2ac2dfa
docs: Improve formatting and clarity in README
dev-ali-mansour Nov 18, 2025
d899579
docs: Update runner configuration in codelab
dev-ali-mansour Nov 18, 2025
9581546
docs: Update runner configuration in codelab
dev-ali-mansour Nov 18, 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
59 changes: 59 additions & 0 deletions .github/workflows/codelab.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Build and Deploy Codelab

on:
push:
branches:
- main
paths:
- 'codelab/codelab.md'

workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

jobs:
build:
name: Build Codelab
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.21'

- name: Install claat
run: go install github.com/googlecodelabs/tools/claat@latest

- name: Build Codelab HTML
run: |
# Add go bin to the workflow's PATH
export PATH="$PATH:$(go env GOPATH)/bin"

# Run claat.
claat export codelab/codelab.md

# The output folder name is unchanged, it comes from the 'id'
mv my-tasks-github-actions-codelab dist

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './dist'

deploy:
name: Deploy Codelab
needs: build
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
98 changes: 87 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
# My Tasks

A modern Android task management app built with Kotlin, Jetpack Compose, and a clean architecture approach. It demonstrates best practices around state-driven UIs, Kotlin coroutines and flows, Room persistence, and dependency injection with Koin.
A modern Android task management app built with Kotlin, Jetpack Compose, and a clean architecture
approach. It demonstrates best practices around state-driven UIs, Kotlin coroutines and flows, Room
persistence, and dependency injection with Koin.

## Screenshots

<p>
<img src="/codelab/images/1.webp" width="200" alt="Screenshot 1"/>
<img src="/codelab/images/2.webp" width="200" alt="Screenshot 2"/>
<img src="/codelab/images/3.webp" width="200" alt="Screenshot 3"/>
<img src="/codelab/images/4.webp" width="200" alt="Screenshot 4"/>
</p>

## Features

Expand All @@ -16,23 +27,29 @@ A modern Android task management app built with Kotlin, Jetpack Compose, and a c
## Tech Stack

**Language & Runtime**

- Kotlin
- Coroutines & Flows

**Android & Jetpack**

- Jetpack Compose UI
- Navigation-Compose
- ViewModel / Lifecycle
- Room for local data storage

**Architecture**

- Clean-ish layered architecture:
- `core/domain`: use cases and domain models
- `core/data`: repositories and Room entities/DAOs
- `feature/*`: UI + ViewModels per feature (home, task details, new task, update task)
- Unidirectional data flow: `Event -> ViewModel -> State/Effect -> UI`
- `core/domain`: use cases and domain models
- `core/data`: repositories and Room entities/DAOs
- `core/ui`: shared UI components, theme, and helper functions
- `feature/*`: UI + ViewModels per feature (home, task details, new task, update task)
- Unidirectional data flow using Model-View-Intent (MVI):
`Event -> ViewModel -> State/Effect -> UI`

**Dependency Injection & Tools**

- Koin (with KSP for DI code generation)
- JUnit 5
- MockK
Expand Down Expand Up @@ -70,18 +87,76 @@ My-Tasks/
- Android Studio Giraffe or newer
- Gradle wrapper (provided in the repo)

> Note: Newer Java versions (e.g., 25) may not be fully supported by the Kotlin/Gradle toolchain used here. If you see errors like `java.lang.IllegalArgumentException: 25.0.1`, run Gradle with JDK 21.
> Note: Newer Java versions (e.g., 25) may not be fully supported by the Kotlin/Gradle toolchain
> used here. If you see errors like `java.lang.IllegalArgumentException: 25.0.1`, run Gradle with JDK
21.

## Getting Started

### 1. Clone the repository
### 1. Fork the repository (Optional - for contributors)

If you want to contribute or set up your own version with CI/CD:

1. Click the **Fork** button at the top right of this repository
2. Clone your forked repository:

```bash
git clone https://github.com/YOUR_USERNAME/My-Tasks.git
cd My-Tasks
```

#### Setting up GitHub Secrets

To enable the automated workflows (Pull Request checks and Google Play deployment), you need to
configure the following secrets in your repository settings:

1. Go to your forked repository on GitHub
2. Navigate to **Settings** → **Secrets and variables** → **Actions**
3. Click **New repository secret** and add each of the following:

**Required Secrets for Release Workflow:**

| Secret Name | Description | How to Get It |
|-------------------------|------------------------------------|--------------------------------------------------|
| `KEYSTORE_PASSWORD` | Password for your Android keystore | The password you set when creating your keystore |
| `KEY_ALIAS` | Alias name for your signing key | The alias you used when creating your key |
| `KEY_PASSWORD` | Password for your signing key | The password you set for your signing key |
| `ANDROID_KEYSTORE` | Base64-encoded keystore file | Run: `base64 -w 0 your-keystore.jks` |
| `GOOGLE_PLAY_AUTH_JSON` | Google Play service account JSON | Download from Google Play Console → API access |

**Creating an Android Keystore (if you don't have one):**

```bash
keytool -genkey -v -keystore release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias your-key-alias
```

**Encoding the Keystore for GitHub Secrets:**

```bash
base64 -w 0 release-key.jks > keystore-base64.txt
# Copy the content of keystore-base64.txt and paste it as ANDROID_KEYSTORE secret
```

**Setting up Google Play Service Account:**

1. Follow this tutorial
to [create a Google Play service account](https://medium.com/automating-react-native-app-release-to-google-play/create-google-play-service-account-68471d4b398b)
2. Download the JSON key file
3. Copy the entire JSON content and paste it as `GOOGLE_PLAY_AUTH_JSON` secret

> **Note:** The release workflow will only trigger when you push a tag (e.g., `v1.0.0`). The pull
> request workflow runs automatically on PRs to `main` or `develop` branches.

### 2. Clone the repository (Simple setup)

If you just want to build and run the app locally without CI/CD:

```bash
git clone https://github.com/dev-ali-mansour/My-Tasks.git
cd My-Tasks
```

### 2. Configure Java
### 3. Configure Java

On Linux, if you have JDK 21 installed at `/usr/lib/jvm/java-21-openjdk`:

Expand All @@ -93,13 +168,13 @@ java -version

You should see a Java 21 version.

### 3. Open in Android Studio
### 4. Open in Android Studio

1. `File` → `Open...`
2. Select the `My-Tasks` folder.
3. Let Gradle sync and KSP/Room code generation complete.

### 4. Build and run
### 5. Build and run

From Android Studio:

Expand Down Expand Up @@ -186,5 +261,6 @@ A clean rebuild can often resolve stale generated code:

## License

This project is provided as-is for learning and demonstration purposes. Adapt licensing as needed for your use case.
This project is provided as-is for learning and demonstration purposes. Adapt licensing as needed
for your use case.

1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ dependencies {
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.navigation)
implementation(libs.splashScreen)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.lifecycle.viewModelCompose)
implementation(libs.multidex)
Expand Down
3 changes: 1 addition & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.MyTasks"
android:theme="@style/Theme.MyTasks.MySplash"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
25 changes: 24 additions & 1 deletion app/src/main/java/dev/alimansour/mytasks/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
package dev.alimansour.mytasks

import android.graphics.Color
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.lifecycleScope
import dev.alimansour.mytasks.core.ui.theme.MyTasksTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
var isLoadingSplash = true
lifecycleScope.launch {
delay(timeMillis = 1500L)
isLoadingSplash = false
}

installSplashScreen().setKeepOnScreenCondition {
isLoadingSplash
}

enableEdgeToEdge(
navigationBarStyle =
SystemBarStyle.light(
Color.TRANSPARENT,
Color.TRANSPARENT,
),
)

setContent {
MyTasksTheme {
MainApp()
Expand Down
20 changes: 20 additions & 0 deletions app/src/main/res/values-night/themes.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="Theme.MyTasks" parent="android:Theme.Material.Light.NoActionBar">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowNoTitle">true</item>
</style>

<style name="Theme.MyTasks.MySplash" parent="Theme.SplashScreen">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowNoTitle">true</item>
<item name="windowSplashScreenBackground">@color/splash_background_dark</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher_round</item>
<item name="postSplashScreenTheme">@style/Theme.MyTasks</item>
</style>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,8 @@
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>

<color name="splash_background_light">#FFFFFFFF</color>
<!-- <color name="splash_background_light">#FFE8F5E9</color>-->
<color name="splash_background_dark">#FF0D1F0D</color>
</resources>
17 changes: 16 additions & 1 deletion app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>

<style name="Theme.MyTasks" parent="android:Theme.Material.Light.NoActionBar" />
<style name="Theme.MyTasks" parent="android:Theme.Material.Light.NoActionBar">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowNoTitle">true</item>
</style>

<style name="Theme.MyTasks.MySplash" parent="Theme.SplashScreen">
<item name="windowActionBar">false</item>
<item name="android:windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowNoTitle">true</item>
<item name="windowSplashScreenBackground">@color/splash_background_light</item>
<item name="windowSplashScreenAnimatedIcon">@mipmap/ic_launcher_round</item>
<item name="postSplashScreenTheme">@style/Theme.MyTasks</item>
</style>
</resources>
Loading
Loading