Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add basic series widget #1094

Merged
merged 17 commits into from
Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ dependencies {
implementation(project(":features:login"))
implementation(project(":features:search"))
implementation(project(":features:series"))
implementation(project(":features:serieswidget"))
implementation(project(":features:settings"))
implementation(project(":libraries:core"))
implementation(project(":libraries:database"))
Expand All @@ -89,6 +90,7 @@ dependencies {
implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.core)
implementation(libs.androidx.fragment)
implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.hilt.work)
implementation(libs.androidx.lifecycle.extensions)
implementation(libs.androidx.lifecycle.livedata)
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/com/chesire/nekome/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ import androidx.work.Configuration
import com.chesire.lifecyklelog.LifecykleLog
import com.chesire.lifecyklelog.LogHandler
import com.chesire.nekome.core.preferences.ApplicationPreferences
import com.chesire.nekome.services.DataRefreshNotifier
import com.chesire.nekome.services.WorkerQueue
import dagger.hilt.android.HiltAndroidApp
import javax.inject.Inject
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import timber.log.Timber

/**
Expand All @@ -27,6 +31,10 @@ class App : Application(), Configuration.Provider {
@Inject
lateinit var workerQueue: WorkerQueue

@Inject
lateinit var dataRefreshNotifier: DataRefreshNotifier

@OptIn(DelicateCoroutinesApi::class)
override fun onCreate() {
super.onCreate()

Expand All @@ -46,6 +54,9 @@ class App : Application(), Configuration.Provider {
workerQueue.enqueueAuthRefresh()
workerQueue.enqueueSeriesRefresh()
workerQueue.enqueueUserRefresh()
GlobalScope.launch {
dataRefreshNotifier.initialize()
}
}

override fun getWorkManagerConfiguration() =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.chesire.nekome.services

import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import com.chesire.nekome.datasource.series.SeriesRepository
import javax.inject.Inject
import javax.inject.Singleton

private const val WIDGET_DATA_NOTIFY_TAG = "WidgetData"
private const val WIDGET_DATA_UNIQUE_NAME = "WidgetSync"

@Singleton
class DataRefreshNotifier @Inject constructor(
private val workManager: WorkManager,
private val seriesRepository: SeriesRepository
) {

/**
* Initialize the notifier and listen to any data updates.
*/
suspend fun initialize() {
seriesRepository.getSeries().collect {
val request = OneTimeWorkRequestBuilder<WidgetDataWorker>()
.addTag(WIDGET_DATA_NOTIFY_TAG)
.build()

workManager.enqueueUniqueWork(
WIDGET_DATA_UNIQUE_NAME,
ExistingWorkPolicy.REPLACE,
request
)
}
}
}
18 changes: 18 additions & 0 deletions app/src/main/java/com/chesire/nekome/services/WidgetDataWorker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.chesire.nekome.services

import android.content.Context
import androidx.glance.appwidget.updateAll
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.chesire.nekome.feature.serieswidget.ui.SeriesWidget

class WidgetDataWorker(
private val context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {

override suspend fun doWork(): Result {
SeriesWidget().updateAll(context)
return Result.success()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color

internal val DarkColorPalette = darkColorScheme(
val DarkColorPalette = darkColorScheme(
primary = Color(0xFF65d3ff),
onPrimary = Color(0xFF003546),
primaryContainer = Color(0xFF004d64),
Expand All @@ -30,7 +30,7 @@ internal val DarkColorPalette = darkColorScheme(
onSurfaceVariant = Color(0xFFc0c8cd)
)

internal val LightColorPalette = lightColorScheme(
val LightColorPalette = lightColorScheme(
primary = Color(0xFF006783),
onPrimary = Color(0xFFffffff),
primaryContainer = Color(0xFFbde9ff),
Expand Down
3 changes: 3 additions & 0 deletions core/resources/src/main/res/values-ja/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@
<string name="series_detail_failure">シリーズ%sの更新に失敗しました。再試行してください</string>
<string name="series_detail_delete">デリート</string>

<string name="series_widget_description">アニメ一覧</string>
<string name="series_widget_increment">+1</string>

<string name="rating_none">評価なし</string>

<string name="login_username">Kitsu eメール</string>
Expand Down
3 changes: 3 additions & 0 deletions core/resources/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@
<string name="series_detail_failure">Failed to update series %s, please try again</string>
<string name="series_detail_delete">Delete</string>

<string name="series_widget_description">Anime list</string>
<string name="series_widget_increment">+1</string>

<string name="rating_none">No rating</string>

<string name="login_username">Kitsu email</string>
Expand Down
1 change: 1 addition & 0 deletions features/serieswidget/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
40 changes: 40 additions & 0 deletions features/serieswidget/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.google.dagger.hilt.android)
alias(libs.plugins.google.devtools.ksp)
alias(libs.plugins.kotlin.android)
}

android {
namespace = "com.chesire.nekome.feature.serieswidget"
compileSdk = libs.versions.sdk.get().toInt()

defaultConfig {
minSdk = 21

consumerProguardFiles("consumer-rules.pro")
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}
}

dependencies {
implementation(project(":core:compose"))
implementation(project(":core:preferences"))
implementation(project(":core:resources"))
implementation(project(":libraries:core"))
implementation(project(":libraries:datasource:series"))

implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.glance.material3)
implementation(libs.bundles.compose)
implementation(libs.google.hilt.android)
implementation(libs.kotlin.result)
implementation(libs.timber)
debugImplementation(libs.androidx.compose.ui.tooling)
ksp(libs.google.hilt.android.compiler)
}
Empty file.
16 changes: 16 additions & 0 deletions features/serieswidget/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<receiver
android:name="GlanceDataReceiver"
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/series_widget_info" />
</receiver>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.chesire.nekome.feature.serieswidget

import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetReceiver
import com.chesire.nekome.feature.serieswidget.ui.SeriesWidget

class GlanceDataReceiver : GlanceAppWidgetReceiver() {
override val glanceAppWidget: GlanceAppWidget
get() = SeriesWidget()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.chesire.nekome.feature.serieswidget

import com.chesire.nekome.feature.serieswidget.ui.SeriesWidgetViewModel
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent

@EntryPoint
@InstallIn(SingletonComponent::class)
interface SeriesWidgetEntryPoint {

fun seriesWidgetViewModel(): SeriesWidgetViewModel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.chesire.nekome.feature.serieswidget.core

import com.chesire.nekome.core.flags.SeriesType
import com.chesire.nekome.core.flags.UserSeriesStatus
import com.chesire.nekome.core.preferences.SeriesPreferences
import com.chesire.nekome.core.preferences.flags.SortOption
import com.chesire.nekome.datasource.series.SeriesDomain
import com.chesire.nekome.datasource.series.SeriesRepository
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

class RetrieveSeriesUseCase @Inject constructor(
private val seriesRepository: SeriesRepository,
private val pref: SeriesPreferences
) {

suspend operator fun invoke(): Flow<List<SeriesDomain>> {
val sortOption = pref.sort.first()
return seriesRepository
.getSeries()
.map { seriesList ->
seriesList
.filter { series -> series.type == SeriesType.Anime }
.filter { series -> series.userSeriesStatus == UserSeriesStatus.Current }
.filter { series -> series.totalLength == 0 || series.progress < series.totalLength }
.sortedWith(
when (sortOption) {
SortOption.Default -> compareBy { it.userId }
SortOption.Title -> compareBy { it.title }
SortOption.StartDate -> compareBy { it.startDate }
SortOption.EndDate -> compareBy { it.endDate }
SortOption.Rating -> compareBy { it.rating }
}
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.chesire.nekome.feature.serieswidget.core

import com.chesire.nekome.datasource.series.SeriesRepository
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.mapEither
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

class UpdateSeriesUseCase @Inject constructor(
private val seriesRepository: SeriesRepository
) {

suspend operator fun invoke(seriesId: Int): Result<Unit, Unit> {
val currentSeries = seriesRepository.getSeries(seriesId)
return withContext(Dispatchers.IO) {
seriesRepository
.updateSeries(
currentSeries.userId,
currentSeries.progress + 1,
currentSeries.userSeriesStatus,
currentSeries.rating
)
.mapEither(
success = { Unit },
failure = { Unit }
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.chesire.nekome.feature.serieswidget.ui

import com.chesire.nekome.datasource.series.SeriesDomain
import javax.inject.Inject

class DomainMapper @Inject constructor() {

fun toSeries(domain: SeriesDomain): Series {
return Series(
userId = domain.userId,
title = domain.title,
progress = buildProgress(domain.progress, domain.totalLength),
isUpdating = false
)
}

private fun buildProgress(progress: Int, totalLength: Int): String {
val maxLengthString = if (totalLength == 0) "-" else totalLength
return "$progress / $maxLengthString"
}
}
Loading