# Custom WeekView with Jetpack Compose

In this blog post, we will learn how to create a custom week view using Jetpack Compose in Android. Jetpack Compose is a modern toolkit for building native Android UI. It simplifies and accelerates UI development on Android with less code, powerful tools, and intuitive Kotlin APIs.

## **Part 1: Setting up the Project**

Before we start, make sure you have the latest version of Android Studio installed.

### **Step 1: Create a new Android project**

Open Android Studio and create a new project. Choose the "Empty Compose Activity" template. Name the project "CustomWeekView".

### **Step 2: Add dependencies**

In the `build.gradle` file of your app module, add the following dependencies:

```kotlin
dependencies {
    implementation("androidx.core:core-ktx:1.9.0")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
    implementation("androidx.activity:activity-compose:1.7.0")
    implementation(platform("androidx.compose:compose-bom:2023.03.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
    implementation("com.jakewharton.threetenabp:threetenabp:1.3.1")
}
```

In project level build.gradle

```kotlin
plugins {
    id("com.android.application") version "8.1.0" apply false
    id("org.jetbrains.kotlin.android") version "1.8.10" apply false
}
```

**Structure:**

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690534039584/5ee87968-c158-40f5-80e6-376234cd97f7.png align="center")

### **Step 3: Initialize ThreeTenABP**

We will be using the ThreeTenABP library for date and time handling. Initialize it in your custom `Application` class:

```kotlin
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        AndroidThreeTen.init(this)
    }
}
```

And declare it in your `AndroidManifest.xml`:

```kotlin
<application
    android:allowBackup="true"
    android:name=".MyApp"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/Theme.LearnAndroidCompose"
    tools:targetApi="31">
    <activity
        android:name=".MainActivity"
        android:exported="true"
        android:label="@string/app_name"
        android:theme="@style/Theme.LearnAndroidCompose">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>
```

## **Part 2: Building the WeekView**

In this part, we will start building our custom week view. We will create a `CalendarView` composable function that will display the week view.

### **Step 1: Create the MainActivity**

In your `MainActivity`, set the content to our `CalendarView` composable. We will pass a `selectedDate` state to it, which will be updated when a date is clicked.

```kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LearnAndroidComposeTheme {
                var selectedDate = remember { mutableStateOf(LocalDate.now()) }
                // A surface container using the 'background' color from the theme
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    CalendarView(selectedDate = selectedDate)
                }
            }
        }
    }
}
```

### **Step 2: Create the CalendarView Composable**

In your `CalendarView` composable, we will display the selected date and the week view. We will use a `HorizontalPager` to allow the user to swipe through weeks.

```kotlin
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun CalendarView(selectedDate: MutableState<LocalDate>) {
    val today = LocalDate.now()
    Log.e("TAG", "CalendarView: $today")

    val weeks = getWeeksFromToday(today, 52)

    val pagerState = rememberPagerState(
        initialPage = 0,
        initialPageOffsetFraction = 0f
    )

    Column(modifier = Modifier.fillMaxWidth()) {
        // Display the selected date and week view here...
    }
}
```

### **Step 3: Display the Selected Date**

Inside your `CalendarView` composable, add a `Text` composable to display the selected date. We will format the date based on whether it is today, tomorrow, or another day.

```kotlin
val displayText = when {
    selectedDate.value == today -> "Today"
    selectedDate.value == today.plusDays(1) -> "Tomorrow"
    else -> {
        val dateFormat = if (selectedDate.value.year == today.year) {
            DateTimeFormatter.ofPattern("d MMM", Locale.getDefault())
        } else {
            DateTimeFormatter.ofPattern("d MMM yyyy", Locale.getDefault())
        }
        selectedDate.value.format(dateFormat)
    }
}

Text(
    text = displayText,
    style = MaterialTheme.typography.headlineLarge.copy( fontWeight = FontWeight.Bold),
    color = MaterialTheme.colorScheme.primary,
    modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp)
)
```

### **Step 4: Display the Week View**

Next, add a `HorizontalPager` to display the week view. Inside the `HorizontalPager`, create a `Row` for each week and a `Box` for each date. When a date is clicked, update the `selectedDate` state.

```kotlin
HorizontalPager(    pageCount = weeks.size,
    state = pagerState, modifier = Modifier.fillMaxWidth()) { page ->
    val weekDates = weeks[page]

    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(horizontal = 8.dp)  // Add horizontal padding
    ) {
        weekDates.forEach { date ->
            val interactionSource = remember { MutableInteractionSource() }
            Box(
                modifier = Modifier
                    .weight(1f)
                    .height(48.dp)  // Add a fixed height
                    .clip(CircleShape)
                    .clickable(
                    ) { selectedDate.value = date },
                    
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = date.dayOfMonth.toString(),
                    textAlign = TextAlign.Center
                )
            }
        }
    }
}
```

## **Part 3: Enhancing the WeekView**

In this part, we will enhance our week view by adding day names and handling date selection.

### **Step 1: Display Day Names**

Before the `HorizontalPager` in your `CalendarView` composable, add a `Row` to display the day names.

```kotlin
Row(
    modifier = Modifier
        .fillMaxWidth()
        .padding(horizontal = 8.dp)
) {
    listOf("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat").forEach { day ->
        Text(
            text = day,
            modifier = Modifier.weight(1f),
            textAlign = TextAlign.Center
        )
    }
}
Spacer(modifier = Modifier.height(8.dp))
```

### **Step 2: Highlight Selected Date**

In the `Box` for each date, change the background color based on whether the date is the selected date.

```kotlin
Box(
    modifier = Modifier
        .weight(1f)
        .height(48.dp)  // Add a fixed height
        .clip(CircleShape)
        .clickable(
        ) { selectedDate.value = date }
        .background(if (date == selectedDate.value) MaterialTheme.colorScheme.primaryContainer else Color.Transparent),

    contentAlignment = Alignment.Center
) {
    Text(
        text = date.dayOfMonth.toString(),
        textAlign = TextAlign.Center
    )
}
```

### **Step 3: Generate Weeks**

Create a function `getWeeksFromToday` to generate a list of weeks from today. Each week is a list of dates.

```kotlin
fun getWeeksFromToday(today: LocalDate, weeksCount: Int): List<List<LocalDate>> {
    val weeks = mutableListOf<List<LocalDate>>()
    var currentStartOfWeek = today
    while (currentStartOfWeek.dayOfWeek != DayOfWeek.SUNDAY) {
        currentStartOfWeek = currentStartOfWeek.minusDays(1)
    }
    repeat(weeksCount) {
        val week = (0 until 7).map { currentStartOfWeek.plusDays(it.toLong()) }
        weeks.add(week)
        currentStartOfWeek = currentStartOfWeek.plusWeeks(1)
    }
    weeks.forEach { week ->
        Log.e("TAG", "Week: ${week.joinToString(", ")}")
    }
    return weeks
}
```

Call this function in your `CalendarView` composable to generate the weeks.

```kotlin
val weeks = getWeeksFromToday(today, 52)
```

### **Step 4: Display Week Name**

Below the displayed date, add a `Text` composable to display the name of the week of the selected date.

```kotlin
val weekName = selectedDate.value.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault())
Text(
    text = weekName,
    color = MaterialTheme.colorScheme.secondary,
    style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Light),
    modifier = Modifier.padding(start = 20.dp, bottom = 16.dp)
)
```

### Complete Code:

**MainActivity.class:**

```kotlin
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LearnAndroidComposeTheme {
                var selectedDate = remember { mutableStateOf(LocalDate.now()) }
                // A surface container using the 'background' color from the theme
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    CalendarView(selectedDate = selectedDate)

                }
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
fun CalendarView(selectedDate: MutableState<LocalDate>) {
    val today = LocalDate.now()
    Log.e("TAG", "CalendarView: $today")

    val weeks = getWeeksFromToday(today, 52)

    val pagerState = rememberPagerState(
        initialPage = 0,
        initialPageOffsetFraction = 0f
    )

    Column(modifier = Modifier.fillMaxWidth()) {
        val displayText = when {
            selectedDate.value == today -> "Today"
            selectedDate.value == today.plusDays(1) -> "Tomorrow"
            else -> {
                val dateFormat = if (selectedDate.value.year == today.year) {
                    DateTimeFormatter.ofPattern("d MMM", Locale.getDefault())
                } else {
                    DateTimeFormatter.ofPattern("d MMM yyyy", Locale.getDefault())
                }
                selectedDate.value.format(dateFormat)
            }
        }

        Text(
            text = displayText,
            style = MaterialTheme.typography.headlineLarge.copy( fontWeight = FontWeight.Bold),
            color = MaterialTheme.colorScheme.primary,
            modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 8.dp)
        )

        val weekName = selectedDate.value.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault())
        Text(
            text = weekName,
            color = MaterialTheme.colorScheme.secondary,
            style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Light),
            modifier = Modifier.padding(start = 20.dp, bottom = 16.dp)
        )

        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 8.dp)
        ) {
            listOf("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat").forEach { day ->
                Text(
                    text = day,
                    modifier = Modifier.weight(1f),
                    textAlign = TextAlign.Center
                )
            }
        }
        Spacer(modifier = Modifier.height(8.dp))

        HorizontalPager(    pageCount = weeks.size,
            state = pagerState, modifier = Modifier.fillMaxWidth()) { page ->
            val weekDates = weeks[page]

            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(horizontal = 8.dp)  // Add horizontal padding
            ) {
                weekDates.forEach { date ->
                    val interactionSource = remember { MutableInteractionSource() }
                    Box(
                        modifier = Modifier
                            .weight(1f)
                            .height(48.dp)  // Add a fixed height
                            .clip(CircleShape)
                            .clickable(
                            ) { selectedDate.value = date }
                            .background(if (date == selectedDate.value) MaterialTheme.colorScheme.primaryContainer else Color.Transparent),

                        contentAlignment = Alignment.Center
                    ) {
                        Text(
                            text = date.dayOfMonth.toString(),
                            textAlign = TextAlign.Center
                        )
                    }
                }
            }
        }
    }
}

fun getWeeksFromToday(today: LocalDate, weeksCount: Int): List<List<LocalDate>> {
    val weeks = mutableListOf<List<LocalDate>>()
    var currentStartOfWeek = today
    while (currentStartOfWeek.dayOfWeek != DayOfWeek.SUNDAY) {
        currentStartOfWeek = currentStartOfWeek.minusDays(1)
    }
    repeat(weeksCount) {
        val week = (0 until 7).map { currentStartOfWeek.plusDays(it.toLong()) }
        weeks.add(week)
        currentStartOfWeek = currentStartOfWeek.plusWeeks(1)
    }
    weeks.forEach { week ->
        Log.e("TAG", "Week: ${week.joinToString(", ")}")
    }
    return weeks
}
```

### **Step 4: Testing the WeekView**

Now, run your app and test the week view. You should be able to swipe through weeks and select a date. The displayed date and week name should update accordingly.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1690534349742/170e2a11-7245-44c8-ba04-9ac78436a5a4.png align="center")

That's it! You have created a custom week view using Jetpack Compose. This is a basic implementation and there are many ways you can enhance it. For example, you can add animations when a date is selected, or you can add events to the dates.

I hope you found this tutorial helpful. Happy coding!
