diff --git a/README.md b/README.md new file mode 100644 index 0000000..97fc0dd --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# JetPackCompose_Basic +This repository useful to learn basic to intermediate level of Jetpack compose. Jetpack compose(JC) is a modern ui development toolkit. It serves as a beginner-friendly project, providing an introduction to Jetpack Compose for newcomers and go upto Intermediate level. + +# Jetpacl Glance + +Jetpack Glance is a framework built on top of the Jetpack Compose runtime that lets you develop and design app widgets using Kotlin APIs. App widgets are miniature application views that can be embedded in other applications and receive periodic updates. + + +Glance provides a set of composables to help you build responsive widgets for the home screen quickly and with less code. The pages in this doc set describe how to use Glance to build app widgets. diff --git a/app/build.gradle b/app/build.gradle index 75677b5..9017aae 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,12 +5,12 @@ plugins { android { namespace 'com.lahsuak.apps.jetpackcomposebasic' - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "com.lahsuak.apps.jetpackcomposebasic" minSdk 23 - targetSdk 33 + targetSdk 34 versionCode 1 versionName "1.0" @@ -27,17 +27,17 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } buildFeatures { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.1.1' + kotlinCompilerExtensionVersion '1.4.0' } packagingOptions { resources { @@ -48,16 +48,19 @@ android { dependencies { - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' - implementation 'androidx.activity:activity-compose:1.6.1' + implementation 'androidx.core:core-ktx:1.10.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' + implementation 'androidx.activity:activity-compose:1.7.2' implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" - implementation 'androidx.compose.material3:material3:1.1.0-alpha03' + implementation 'androidx.compose.material3:material3:1.2.0-alpha03' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.4' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" + + implementation 'androidx.glance:glance:1.0.0-beta01' + implementation "androidx.glance:glance-appwidget:1.0.0-beta01" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5ebfd06..f9f7962 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,7 +15,6 @@ @@ -27,6 +26,14 @@ android:name="android.app.lib_name" android:value="" /> + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt index 187ee32..8c2dc49 100644 --- a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/MainActivity.kt @@ -1,16 +1,34 @@ package com.lahsuak.apps.jetpackcomposebasic import android.os.Bundle +import android.text.format.DateFormat import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.lahsuak.apps.jetpackcomposebasic.ui.theme.JetPackComposeBasicTheme +import kotlinx.coroutines.delay class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -22,29 +40,50 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - Greeting("Android") + Clock() } } } } } -/*** -Composable functions : -A composable function is a regular function annotated with @Composable. -This enables your function to call other @Composable functions within it. -You can see how the Greeting function is marked as @Composable. -This function will produce a piece of UI hierarchy displaying the given input, -String. Text is a composable function provided by the library. -***/ + @Composable -fun Greeting(name: String) { - Text(text = "Hello $name!") +fun Clock() { + var date by remember { + mutableStateOf( + DateFormat.format("hh:mm:ss a", System.currentTimeMillis()) + ) + } + LaunchedEffect(Unit) { + while (true) { + delay(1000L) + date = DateFormat.format("hh:mm:ss a", System.currentTimeMillis()) + } + } + Column( + modifier = Modifier + .fillMaxSize() + .background(Color.DarkGray), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Widget Demo", style = TextStyle(color = Color.White, fontSize = 24.sp) + ) + Spacer(modifier = Modifier.height(48.dp)) + AnimatedContent(targetState = date) { + Text( + text = it.toString(), + style = TextStyle(color = Color.White, fontSize = 48.sp) + ) + } + } } @Preview(showBackground = true) @Composable fun DefaultPreview() { JetPackComposeBasicTheme { - Greeting("Android") + Clock() } } \ No newline at end of file diff --git a/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/widget/CounterWidget.kt b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/widget/CounterWidget.kt new file mode 100644 index 0000000..6c47cd2 --- /dev/null +++ b/app/src/main/java/com/lahsuak/apps/jetpackcomposebasic/widget/CounterWidget.kt @@ -0,0 +1,133 @@ +package com.lahsuak.apps.jetpackcomposebasic.widget + +import android.content.Context +import android.text.format.DateFormat +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.glance.Button +import androidx.glance.GlanceId +import androidx.glance.GlanceModifier +import androidx.glance.action.ActionParameters +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver +import androidx.glance.appwidget.action.ActionCallback +import androidx.glance.appwidget.action.actionRunCallback +import androidx.glance.appwidget.provideContent +import androidx.glance.appwidget.state.updateAppWidgetState +import androidx.glance.background +import androidx.glance.currentState +import androidx.glance.layout.Alignment +import androidx.glance.layout.Column +import androidx.glance.layout.Row +import androidx.glance.layout.Spacer +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.height +import androidx.glance.layout.width +import androidx.glance.text.FontWeight +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import androidx.glance.unit.ColorProvider +import kotlinx.coroutines.delay + +private const val DATE_FORMAT = "hh:mm:ss a" +private const val COUNT_KEY = "count" + +object CounterWidget : GlanceAppWidget() { + val countKey = intPreferencesKey(COUNT_KEY) + override suspend fun provideGlance(context: Context, id: GlanceId) { + provideContent { + var date by remember { + mutableLongStateOf( + System.currentTimeMillis() + ) + } + val count = currentState(key = countKey) ?: 0 + Column( + modifier = GlanceModifier + .fillMaxSize() + .background(Color.DarkGray), + verticalAlignment = Alignment.Vertical.CenterVertically, + horizontalAlignment = Alignment.Horizontal.CenterHorizontally + ) { + LaunchedEffect(date) { + while (true) { + delay(1000L) + date = System.currentTimeMillis() + } + } + Text( + text = DateFormat.format(DATE_FORMAT, date).toString(), + style = TextStyle(color = ColorProvider(Color.White), fontSize = 24.sp) + ) + Spacer(modifier = GlanceModifier.height(8.dp)) + Text( + text = count.toString(), + style = TextStyle( + fontWeight = FontWeight.Medium, + color = ColorProvider(Color.White), + fontSize = 26.sp + ) + ) + Row { + Button( + text = "+", + onClick = actionRunCallback(IncrementActionCallback::class.java) + ) + Spacer(modifier = GlanceModifier.width(8.dp)) + Button( + text = "-", + onClick = actionRunCallback(DecrementActionCallback::class.java) + ) + } + } + } + } +} + +class CounterWidgetReceiver : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget + get() = CounterWidget +} + +class IncrementActionCallback : ActionCallback { + override suspend fun onAction( + context: Context, + glanceId: GlanceId, + parameters: ActionParameters + ) { + updateAppWidgetState(context, glanceId) { prefs -> + val currentCount = prefs[CounterWidget.countKey] + if (currentCount != null) { + prefs[CounterWidget.countKey] = currentCount + 1 + } else { + prefs[CounterWidget.countKey] = 1 + } + } + CounterWidget.update(context, glanceId) + } +} + +class DecrementActionCallback : ActionCallback { + override suspend fun onAction( + context: Context, + glanceId: GlanceId, + parameters: ActionParameters + ) { + updateAppWidgetState(context, glanceId) { prefs -> + val currentCount = prefs[CounterWidget.countKey] + if (currentCount != null) { + prefs[CounterWidget.countKey] = currentCount - 1 + } else { + prefs[CounterWidget.countKey] = 1 + } + } + CounterWidget.update(context, glanceId) + } +} \ No newline at end of file diff --git a/app/src/main/res/xml/counter_widget_info.xml b/app/src/main/res/xml/counter_widget_info.xml new file mode 100644 index 0000000..89dc664 --- /dev/null +++ b/app/src/main/res/xml/counter_widget_info.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index e868fc8..13d8630 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ buildscript { ext { - compose_version = '1.3.2' + compose_version = '1.4.3' } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.3.0' apply false - id 'com.android.library' version '7.3.0' apply false - id 'org.jetbrains.kotlin.android' version '1.6.10' apply false + id 'com.android.application' version '8.0.2' apply false + id 'com.android.library' version '8.0.2' apply false + id 'org.jetbrains.kotlin.android' version '1.8.0' apply false } \ No newline at end of file