Skip to content

Commit

Permalink
Glance app widget (#229)
Browse files Browse the repository at this point in the history
* Glance app widget

* Glance app widget

* Glance app widget
  • Loading branch information
michaelbel authored Feb 23, 2024
1 parent c3c214c commit fb74d17
Show file tree
Hide file tree
Showing 60 changed files with 1,270 additions and 12 deletions.
1 change: 1 addition & 0 deletions android-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ dependencies {
implementation(project(":core:navigation"))
implementation(project(":core:platform-services:interactor"))
implementation(project(":core:ui"))
implementation(project(":core:widget"))
implementation(project(":core:work"))
implementation(project(":feature:auth"))
implementation(project(":feature:account"))
Expand Down
29 changes: 29 additions & 0 deletions android-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@

</activity>

<activity
android:name="org.michaelbel.movies.widget.configure.AppWidgetConfigureActivity"
android:theme="@style/Theme.Movies"
android:exported="false">

<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter>

</activity>

<activity-alias
android:name="org.michaelbel.movies.RedIcon"
android:enabled="true"
Expand Down Expand Up @@ -178,6 +189,24 @@

</activity-alias>

<receiver
android:name="org.michaelbel.movies.widget.MoviesGlanceWidgetReceiver"
android:exported="true">

<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/movies_appwidget_provider" />

</receiver>

<receiver
android:name="org.michaelbel.movies.widget.configure.AppWidgetPinnedReceiver"
android:exported="false" />

<!-- Per-App Language Preferences (https://d.android.com/guide/topics/resources/app-languages) -->
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import org.michaelbel.movies.ui.theme.MoviesTheme
@Composable
internal fun MainActivityContent(
viewModel: MainViewModel = hiltViewModel(),
enableEdgeToEdge: (SystemBarStyle, SystemBarStyle) -> Unit,
enableEdgeToEdge: (SystemBarStyle, SystemBarStyle) -> Unit
) {
val currentTheme by viewModel.currentTheme.collectAsStateWithLifecycle()
val dynamicColors by viewModel.dynamicColors.collectAsStateWithLifecycle()
Expand Down
1 change: 1 addition & 0 deletions config/detekt/detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ exceptions:
- 'MalformedURLException'
- 'NumberFormatException'
- 'ParseException'
- 'SerializationException'
allowedExceptionNameRegex: '_|(ignore|expected).*'
ThrowingExceptionFromFinally:
active: true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.michaelbel.movies.common.exceptions

data object MoviesUpcomingException: Exception()
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.paging.PagingSource
import kotlinx.coroutines.flow.Flow
import org.michaelbel.movies.common.list.MovieList
import org.michaelbel.movies.persistence.database.entity.MovieDb
import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini

interface MovieInteractor {

Expand All @@ -20,6 +21,8 @@ interface MovieInteractor {

suspend fun movieDetails(pagingKey: String, movieId: Int): MovieDb

suspend fun moviesWidget(): List<MovieDbMini>

suspend fun removeMovies(pagingKey: String)

suspend fun removeMovie(pagingKey: String, movieId: Int)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.michaelbel.movies.interactor.remote.SearchMoviesRemoteMediator
import org.michaelbel.movies.network.model.MovieResponse
import org.michaelbel.movies.persistence.database.AppDatabase
import org.michaelbel.movies.persistence.database.entity.MovieDb
import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini
import org.michaelbel.movies.repository.MovieRepository
import org.michaelbel.movies.repository.PagingKeyRepository
import org.michaelbel.movies.repository.SearchRepository
Expand Down Expand Up @@ -71,6 +72,10 @@ internal class MovieInteractorImpl @Inject constructor(
return movieRepository.moviesFlow(pagingKey, limit)
}

override suspend fun moviesWidget(): List<MovieDbMini> {
return withContext(dispatchers.io) { movieRepository.moviesWidget() }
}

override suspend fun movie(pagingKey: String, movieId: Int): MovieDb {
return withContext(dispatchers.io) { movieRepository.movie(pagingKey, movieId) }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
import org.michaelbel.movies.persistence.database.entity.MovieDb
import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini

/**
* The Data Access Object for the [MovieDb] class.
Expand All @@ -25,7 +26,9 @@ interface MovieDao {
@Query("SELECT * FROM movies WHERE movieList = :movieList ORDER BY position ASC LIMIT :limit")
suspend fun movies(movieList: String, limit: Int): List<MovieDb>

@Transaction
@Query("SELECT * FROM movies WHERE movieList = :movieList ORDER BY position ASC LIMIT :limit")
suspend fun moviesMini(movieList: String, limit: Int): List<MovieDbMini>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMovies(movies: List<MovieDb>)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ data class MovieDb(
companion object {
const val MOVIES_LOCAL_LIST = "movies_local"
const val MOVIES_SEARCH_HISTORY = "movies_search_history"
const val MOVIES_WIDGET = "movies_widget"

val Empty = MovieDb(
movieList = "",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.michaelbel.movies.persistence.database.entity.mini

data class MovieDbMini(
val movieList: String,
val id: Int,
val title: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.Flow
import org.michaelbel.movies.network.model.MovieResponse
import org.michaelbel.movies.network.model.Result
import org.michaelbel.movies.persistence.database.entity.MovieDb
import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini

interface MovieRepository {

Expand All @@ -18,6 +19,8 @@ interface MovieRepository {

suspend fun movieDetails(pagingKey: String, movieId: Int): MovieDb

suspend fun moviesWidget(): List<MovieDbMini>

suspend fun removeMovies(pagingKey: String)

suspend fun removeMovie(pagingKey: String, movieId: Int)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.flow.Flow
import org.michaelbel.movies.common.exceptions.MovieDetailsException
import org.michaelbel.movies.common.exceptions.MoviesUpcomingException
import org.michaelbel.movies.common.list.MovieList
import org.michaelbel.movies.common.localization.LocaleController
import org.michaelbel.movies.network.isTmdbApiKeyEmpty
import org.michaelbel.movies.network.model.MovieResponse
import org.michaelbel.movies.network.model.Result
import org.michaelbel.movies.network.service.movie.MovieService
import org.michaelbel.movies.persistence.database.dao.MovieDao
import org.michaelbel.movies.persistence.database.entity.MovieDb
import org.michaelbel.movies.persistence.database.entity.mini.MovieDbMini
import org.michaelbel.movies.persistence.database.ktx.movieDb
import org.michaelbel.movies.persistence.database.ktx.orEmpty
import org.michaelbel.movies.repository.MovieRepository
import org.michaelbel.movies.repository.ktx.checkApiKeyNotNullException
Expand Down Expand Up @@ -51,11 +55,34 @@ internal class MovieRepositoryImpl @Inject constructor(
override suspend fun movieDetails(pagingKey: String, movieId: Int): MovieDb {
return try {
movieDao.movieById(pagingKey, movieId) ?: movieService.movie(movieId, localeController.language).mapToMovieDb
} catch (e: Exception) {
} catch (ignored: Exception) {
throw MovieDetailsException
}
}

override suspend fun moviesWidget(): List<MovieDbMini> {
return try {
val movieResult = movieService.movies(
list = MovieList.Upcoming.name,
language = localeController.language,
page = 1
)
val moviesDb = movieResult.results.mapIndexed { index, movieResponse ->
movieResponse.movieDb(
movieList = MovieDb.MOVIES_WIDGET,
position = index.plus(1)
)
}
movieDao.removeMovies(MovieDb.MOVIES_WIDGET)
movieDao.insertMovies(moviesDb)
movieDao.moviesMini(MovieDb.MOVIES_WIDGET, MovieResponse.DEFAULT_PAGE_SIZE)
} catch (ignored: Exception) {
movieDao.moviesMini(MovieDb.MOVIES_WIDGET, MovieResponse.DEFAULT_PAGE_SIZE).ifEmpty {
throw MoviesUpcomingException
}
}
}

override suspend fun removeMovies(pagingKey: String) {
movieDao.removeMovies(pagingKey)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import androidx.compose.animation.core.updateTransition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
Expand Down
1 change: 1 addition & 0 deletions core/widget/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
64 changes: 64 additions & 0 deletions core/widget/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
@Suppress("dsl_scope_violation")
plugins {
alias(libs.plugins.library)
alias(libs.plugins.kotlin)
alias(libs.plugins.detekt)
id("movies-android-hilt")
id("org.jetbrains.kotlin.plugin.serialization")
}

android {
namespace = "org.michaelbel.movies.widget"

defaultConfig {
minSdk = libs.versions.min.sdk.get().toInt()
compileSdk = libs.versions.compile.sdk.get().toInt()
}

/*buildTypes {
create("benchmark") {
signingConfig = signingConfigs.getByName("debug")
matchingFallbacks += listOf("release")
initWith(getByName("release"))
}
}*/

buildFeatures {
compose = true
}

composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get()
}

compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
freeCompilerArgs = freeCompilerArgs + listOf(
"-opt-in=androidx.compose.material3.ExperimentalMaterial3Api"
)
}

lint {
quiet = true
abortOnError = false
ignoreWarnings = true
checkDependencies = true
lintConfig = file("${project.rootDir}/config/codestyle/lint.xml")
}
}

dependencies {
implementation(project(":core:interactor"))
implementation(project(":core:common"))
implementation(project(":core:ui"))
implementation(project(":core:work"))
implementation(libs.bundles.glance)
implementation(libs.bundles.datastore)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.kotlin.serialization.json)
ksp(libs.androidx.hilt.compiler)
}
2 changes: 2 additions & 0 deletions core/widget/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.michaelbel.movies.widget

import android.content.Context
import androidx.glance.GlanceId
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.provideContent
import androidx.glance.currentState
import org.michaelbel.movies.widget.theme.MoviesGlanceTheme
import org.michaelbel.movies.widget.work.MoviesGlanceStateDefinition
import org.michaelbel.movies.widget.work.MoviesWidgetState

internal class MoviesGlanceWidget: GlanceAppWidget() {

override suspend fun provideGlance(context: Context, id: GlanceId) {
provideContent {
val glanceWidgetState = currentState<MoviesWidgetState>()
MoviesGlanceTheme {
MoviesGlanceWidgetContent(
glanceWidgetState = glanceWidgetState
)
}
}
}

override val stateDefinition = MoviesGlanceStateDefinition
}
Loading

0 comments on commit fb74d17

Please sign in to comment.