Skip to content

Commit

Permalink
Implement show Pinned Pairs in App Widget (#100)
Browse files Browse the repository at this point in the history
* Add dependency

* Provide require dependencies

* Provide widget

* Implement UI of widget

* Rename widget

* Extract composable

* Implement widget action

* Ui tweak

* Improve widget UI and handle app open action

* Remove unused imports

* Replace class and mapping value

* Update value to display

* Fix wrong display item

* Improve coding style

* Remove unused imports

* Improve coding style

* Updae size for composable

* Update spacing

* Display calculators with multiple to amounts

* Improve list mapping

* Create event handler in widget

* Simplify item

* Trigger widget

* Trigger Navigate to add new pair

* Update when launch app

* Handle navigate back and persist across instances

* Create worker for quick pairs refresh

* Update widget

* Create worker for rates refresh

* Update code

* Refactor request creation

* Rename class

* Improve code for rates refresh

* Display app logo

* Rename

* Use app context instead

* Avoid duplicate sending broadcast

* Avoid duplicate sending broadcast
  • Loading branch information
hieuwu authored Sep 21, 2024
1 parent 8ac27ad commit e12ebb0
Show file tree
Hide file tree
Showing 17 changed files with 524 additions and 13 deletions.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ dependencies {
implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

implementation "com.google.dagger:dagger:2.50"
implementation 'androidx.glance:glance-appwidget:1.1.0'
ksp "com.google.dagger:dagger-compiler:2.50"

implementation "androidx.room:room-runtime:2.6.1"
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@
</intent-filter>
</activity>

<receiver android:name=".presentation.quick.glancewidget.QuickPairsWidgetReceiver"
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/glance_widget_provider" />
</receiver>

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="dev.arkbuilders.rate.androidx-startup"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ class AppWorkerFactory @Inject constructor(
timestampRepo,
),
)
addFactory(
QuickPairsWidgetRefreshWorkerFactory(),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dev.arkbuilders.rate.data.worker

import android.content.Context
import android.content.Intent
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import dev.arkbuilders.rate.presentation.quick.glancewidget.QuickPairsWidgetReceiver
import timber.log.Timber

class QuickPairsWidgetRefreshWorker(
params: WorkerParameters,
private val context: Context,
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result {
Timber.d("Refresh rates work executed")
val intent =
Intent(applicationContext, QuickPairsWidgetReceiver::class.java).apply {
action = QuickPairsWidgetReceiver.PINNED_PAIRS_REFRESH
}
applicationContext.sendBroadcast(intent)
return Result.success()
}

companion object {
const val NAME = "RatesRefreshWorker"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dev.arkbuilders.rate.data.worker

import android.content.Context
import androidx.work.ListenableWorker
import androidx.work.WorkerFactory
import androidx.work.WorkerParameters

class QuickPairsWidgetRefreshWorkerFactory : WorkerFactory() {
override fun createWorker(
appContext: Context,
workerClassName: String,
workerParameters: WorkerParameters,
): ListenableWorker? {
return when (workerClassName) {
QuickPairsWidgetRefreshWorker::class.java.name ->
QuickPairsWidgetRefreshWorker(
params = workerParameters,
context = appContext,
)

else -> null
}
}
}
6 changes: 6 additions & 0 deletions app/src/main/java/dev/arkbuilders/rate/di/AppComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
import dagger.BindsInstance
import dagger.Component
import dev.arkbuilders.rate.data.repo.PortfolioRepoImpl
import dev.arkbuilders.rate.data.repo.QuickRepoImpl
import dev.arkbuilders.rate.data.repo.currency.CurrencyRepoImpl
import dev.arkbuilders.rate.data.worker.AppWorkerFactory
import dev.arkbuilders.rate.data.worker.CurrencyMonitorWorker
Expand All @@ -14,6 +15,7 @@ import dev.arkbuilders.rate.di.module.RepoModule
import dev.arkbuilders.rate.domain.repo.NetworkStatus
import dev.arkbuilders.rate.domain.repo.Prefs
import dev.arkbuilders.rate.domain.usecase.CalcFrequentCurrUseCase
import dev.arkbuilders.rate.domain.usecase.ConvertWithRateUseCase
import dev.arkbuilders.rate.presentation.pairalert.AddPairAlertViewModelFactory
import dev.arkbuilders.rate.presentation.pairalert.PairAlertViewModelFactory
import dev.arkbuilders.rate.presentation.portfolio.AddAssetViewModelFactory
Expand Down Expand Up @@ -62,6 +64,10 @@ interface AppComponent {

fun assetsRepo(): PortfolioRepoImpl

fun quickRepo(): QuickRepoImpl

fun convertUseCase(): ConvertWithRateUseCase

fun inject(currencyMonitorWorker: CurrencyMonitorWorker)

fun calcFrequentCurrUseCase(): CalcFrequentCurrUseCase
Expand Down
15 changes: 10 additions & 5 deletions app/src/main/java/dev/arkbuilders/rate/presentation/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import android.app.Application
import androidx.work.Configuration
import androidx.work.Constraints
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ListenableWorker
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import com.google.firebase.crashlytics.ktx.crashlytics
import com.google.firebase.ktx.Firebase
import dev.arkbuilders.rate.BuildConfig
import dev.arkbuilders.rate.data.worker.CurrencyMonitorWorker
import dev.arkbuilders.rate.data.worker.QuickPairsWidgetRefreshWorker
import dev.arkbuilders.rate.di.DIManager
import dev.arkbuilders.rate.domain.repo.PreferenceKey
import kotlinx.coroutines.CoroutineScope
Expand All @@ -26,7 +28,8 @@ class App : Application(), Configuration.Provider {
DIManager.init(this)

initCrashlytics()
initWorker()
initWorker(CurrencyMonitorWorker::class.java, CurrencyMonitorWorker.NAME)
initWorker(QuickPairsWidgetRefreshWorker::class.java, QuickPairsWidgetRefreshWorker.NAME)
}

private fun initCrashlytics() =
Expand All @@ -41,24 +44,26 @@ class App : Application(), Configuration.Provider {
Firebase.crashlytics.setCrashlyticsCollectionEnabled(collect)
}

private fun initWorker() {
private fun initWorker(
workerClass: Class<out ListenableWorker?>,
workerName: String,
) {
val workManager = WorkManager.getInstance(this)

val constraints =
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()

val workRequest =
PeriodicWorkRequest.Builder(
CurrencyMonitorWorker::class.java,
workerClass,
8L,
TimeUnit.HOURS,
).setConstraints(constraints)
.build()

workManager.enqueueUniquePeriodicWork(
CurrencyMonitorWorker.NAME,
workerName,
ExistingPeriodicWorkPolicy.KEEP,
workRequest,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package dev.arkbuilders.rate.presentation

import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.core.view.WindowCompat
import dev.arkbuilders.rate.presentation.quick.glancewidget.QuickPairsWidgetReceiver
import dev.arkbuilders.rate.presentation.theme.ARKRateTheme

class MainActivity : ComponentActivity() {
Expand All @@ -18,4 +20,13 @@ class MainActivity : ComponentActivity() {
}
}
}

override fun onStop() {
sendBroadcast(
Intent(this, QuickPairsWidgetReceiver::class.java).apply {
action = QuickPairsWidgetReceiver.PINNED_PAIRS_REFRESH
},
)
super.onStop()
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
@file:OptIn(
ExperimentalComposeUiApi::class,
ExperimentalFoundationApi::class,
)

package dev.arkbuilders.rate.presentation.quick

import androidx.compose.foundation.ExperimentalFoundationApi
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
Expand All @@ -29,12 +26,12 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHostState
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.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
Expand All @@ -61,6 +58,7 @@ import dev.arkbuilders.rate.domain.model.CurrencyName
import dev.arkbuilders.rate.domain.model.PinnedQuickPair
import dev.arkbuilders.rate.domain.model.QuickPair
import dev.arkbuilders.rate.presentation.destinations.AddQuickScreenDestination
import dev.arkbuilders.rate.presentation.quick.glancewidget.action.AddNewPairAction.Companion.ADD_NEW_PAIR
import dev.arkbuilders.rate.presentation.theme.ArkColor
import dev.arkbuilders.rate.presentation.ui.AppButton
import dev.arkbuilders.rate.presentation.ui.AppHorDiv16
Expand Down Expand Up @@ -93,7 +91,15 @@ fun QuickScreen(navigator: DestinationsNavigator) {
val state by viewModel.collectAsState()
val snackState = remember { SnackbarHostState() }
val ctx = LocalContext.current

LaunchedEffect(key1 = Unit) {
val activity = ctx.findActivity()
val intent = activity?.intent
val createNewPair = intent?.getStringExtra(ADD_NEW_PAIR) ?: ""
if (createNewPair.isNotEmpty()) {
navigator.navigate(AddQuickScreenDestination())
intent?.removeExtra(ADD_NEW_PAIR)
}
}
viewModel.collectSideEffect { effect ->
when (effect) {
is QuickScreenEffect.ShowSnackbarAdded ->
Expand Down Expand Up @@ -585,3 +591,10 @@ private fun QuickEmpty(navigator: DestinationsNavigator) {
}
}
}

fun Context.findActivity(): Activity? =
when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package dev.arkbuilders.rate.presentation.quick.glancewidget

import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.layout.Alignment
import androidx.glance.layout.Column
import androidx.glance.layout.Row
import androidx.glance.layout.Spacer
import androidx.glance.layout.fillMaxWidth
import androidx.glance.layout.padding
import androidx.glance.layout.size
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 dev.arkbuilders.rate.data.CurrUtils
import dev.arkbuilders.rate.domain.model.PinnedQuickPair
import dev.arkbuilders.rate.presentation.theme.ArkColor
import dev.arkbuilders.rate.presentation.utils.IconUtils

@Composable
fun QuickPairItem(
quick: PinnedQuickPair,
context: Context,
) {
Row(
modifier = GlanceModifier.padding(vertical = 2.dp),
) {
Image(
modifier = GlanceModifier.size(24.dp),
provider =
ImageProvider(
IconUtils.iconForCurrCode(
context,
quick.pair.from,
),
),
contentDescription = null,
)
Column(
modifier = GlanceModifier.padding(start = 8.dp),
verticalAlignment = Alignment.Vertical.CenterVertically,
) {
Text(
text = "${quick.pair.from} to ${quick.pair.to.joinToString(
separator = ", ",
) { it.code }}",
style =
TextStyle(
color = ColorProvider(ArkColor.TextPrimary),
fontWeight = FontWeight.Medium,
),
)

Text(
text = "${CurrUtils.prepareToDisplay(quick.pair.amount)} ${quick.pair.from} = ",
style =
TextStyle(
color = ColorProvider(ArkColor.TextTertiary),
),
)
for (toAmount in quick.actualTo) {
Row(modifier = GlanceModifier.fillMaxWidth()) {
Image(
modifier = GlanceModifier.size(16.dp),
provider =
ImageProvider(
IconUtils.iconForCurrCode(
context,
toAmount.code,
),
),
contentDescription = null,
)
Spacer(modifier = GlanceModifier.width(4.dp))
Text(
text = "${CurrUtils.prepareToDisplay(toAmount.value)} ${toAmount.code}",
style =
TextStyle(
color = ColorProvider(ArkColor.TextTertiary),
),
)
}
}
}
}
}
Loading

0 comments on commit e12ebb0

Please sign in to comment.