Skip to content
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
6 changes: 6 additions & 0 deletions androidApp/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />

<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />

<application
android:name=".SpendooApp"
Expand All @@ -39,6 +41,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<receiver
android:name="com.spendoo.notifications.data.scheduler.ReminderDelayReceiver"
android:exported="false" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ data class FinancialActionRoute(val tile: String, val body: String, val actionId
data object TopSpendingCategoriesRoute : NavKey

@Serializable
data class CategoryOffersRoute(val categoryId: String) : NavKey
data class CategoryOffersRoute(val categoryId: String?) : NavKey

@Serializable
data class EditTransactionRoute(val transactionId: String) : NavKey
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import com.spendoo.categories.presentation.screen.addCategoryBottomSheet.AddCate
import com.spendoo.categories.presentation.screen.addScheduledPaymentBottomSheet.AddScheduledPaymentViewModel
import com.spendoo.categories.presentation.screen.addTransactionBottomSheet.AddTransactionViewModel
import com.spendoo.categories.presentation.screen.categories.CategoriesViewModel
import com.spendoo.categories.presentation.screen.categoryOffers.CategoryOffersViewModel
import com.spendoo.categories.presentation.screen.categorySelectionSheet.CategorySelectionViewModel
import com.spendoo.categories.presentation.screen.editTransactionBottomSheet.EditTransactionViewModel
import com.spendoo.categories.presentation.screen.financialActionScreen.FinancialActionViewModel
import com.spendoo.categories.presentation.screen.inputVoiceBottomSheet.InputVoiceViewModel
import com.spendoo.categories.presentation.screen.scheduledPaymentDetails.ScheduledPaymentDetailsViewModel
import com.spendoo.categories.presentation.screen.scheduledPayments.ScheduledPaymentsViewModel
import com.spendoo.categories.presentation.screen.transactionDetails.TransactionDetailsViewModel
import com.spendoo.categories.presentation.screen.topSpendingCategories.TopSpendingCategoriesViewModel
import com.spendoo.categories.presentation.screen.categoryOffers.CategoryOffersViewModel
import com.spendoo.categories.presentation.screen.editTransactionBottomSheet.EditTransactionViewModel
import com.spendoo.categories.presentation.screen.transactionDetails.TransactionDetailsViewModel
import org.koin.core.module.dsl.viewModel
import org.koin.core.module.dsl.viewModelOf
import org.koin.dsl.module

Expand All @@ -28,6 +29,13 @@ val categoriesPresentationModule = module {
viewModelOf(::TransactionDetailsViewModel)
viewModelOf(::FinancialActionViewModel)
viewModelOf(::TopSpendingCategoriesViewModel)
viewModelOf(::CategoryOffersViewModel)
viewModelOf(::EditTransactionViewModel)
viewModel { params ->
val (categoryId: String?) = params
CategoryOffersViewModel(
categoryId = categoryId,
transactionsRepository = get(),
offersRepository = get()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import spendoo.designsystem.generated.resources.no_offers_found

@Composable
fun CategoryOffersScreen(
categoryId: String,
categoryId: String?,
viewModel: CategoryOffersViewModel = koinViewModel(parameters = { parametersOf(categoryId) })
) {
val state by viewModel.state.collectAsStateWithLifecycle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class CategoryOffersItemUiState(
)

data class CategoryOffersUiState(
val categoryId: String = "",
val categoryId: String? = null,
val categoryName: String = "",
val isLoading: Boolean = false,
val topItemsWithOffers: List<CategoryOffersItemUiState> = emptyList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import spendoo.designsystem.generated.resources.an_error_occurred
import kotlin.math.absoluteValue

class CategoryOffersViewModel(
private val categoryId: String,
private val categoryId: String?,
private val transactionsRepository: TransactionsRepository,
private val offersRepository: OffersRepository
) : BaseViewModel<CategoryOffersUiState>(CategoryOffersUiState(categoryId = categoryId)),
Expand All @@ -28,7 +28,7 @@ class CategoryOffersViewModel(
tryToCall(
block = {
transactionsRepository.getTopFrequencyItems(
categoryId = categoryId.ifEmpty { null },
categoryId = categoryId?.ifEmpty { null },
pageQuery = PageQuery(page = 0, size = 3)
)
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
<string name="otp_must_be_5_digits">يجب أن يتكون رمز التحقق من 5 أرقام</string>
<string name="an_error_occurred">حدث خطأ</string>
<string name="unknown_error">خطأ غير معروف</string>
<string name="tracking_reminder_title">تتبع إنفاقك</string>
<string name="tracking_reminder_body">لا تنسَ تسجيل نفقاتك اليوم للحفاظ على ميزانيتك في المسار الصحيح!</string>
<string name="periodic_reminder">التنبيهات الدورية</string>
<string name="low">منخفض</string>
<string name="medium">متوسط</string>
<string name="high">مرتفع</string>
Expand Down Expand Up @@ -377,5 +380,6 @@
<string name="select_time">اختر الوقت</string>
<string name="logout">تسجيل الخروج</string>
<string name="payment_required">مطلوب الدفع</string>
<string name="home_offers">عروض الصفحة الرئيسية</string>
</resources>

Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
<string name="otp_must_be_5_digits">OTP must be 5 digits</string>
<string name="an_error_occurred">An error occurred</string>
<string name="unknown_error">Unknown error</string>
<string name="tracking_reminder_title">Track your spending</string>
<string name="tracking_reminder_body">Don&apos;t forget to record your expenses today to keep your budget on track!</string>
<string name="periodic_reminder">Periodic Reminder</string>
<string name="low">Low</string>
<string name="medium">Medium</string>
<string name="high">High</string>
Expand Down Expand Up @@ -377,4 +380,5 @@
<string name="select_time">Select Time</string>
<string name="logout">Logout</string>
<string name="payment_required">Payment Required</string>
<string name="home_offers">Home Offers</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import com.spendoo.designsystem.components.icon.CategoryIcon
import com.spendoo.designsystem.components.icon.Icon
import com.spendoo.designsystem.components.switch.Switch
import com.spendoo.designsystem.components.text.Text
import com.spendoo.designsystem.modifier.clickableNoRipple
import com.spendoo.designsystem.theme.theme.SpendooTheme
Expand Down Expand Up @@ -63,6 +64,40 @@ fun ProfileSettingItem(
}
}

@Composable
fun ProfileSettingItem(
icon: DrawableResource,
title: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
modifier: Modifier = Modifier,
backgroundColor: Color = Theme.colorScheme.background.secondary,
shape: Shape = RoundedCornerShape(16.dp)
) {
Row(
modifier = modifier
.fillMaxWidth()
.background(backgroundColor, shape)
.border(1.dp, Theme.colorScheme.border.primary, shape)
.padding(horizontal = 8.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
CategoryIcon(icon)
Text(
modifier = Modifier.padding(start = 8.dp).weight(1f),
text = title,
style = Theme.typography.body.medium,
color = Theme.colorScheme.text.title,
overflow = TextOverflow.Ellipsis,
maxLines = 1
)
Switch(
checked = checked,
onCheckedChange = onCheckedChange
)
}
}

@PreviewLightDark
@Composable
private fun ProfileSettingItemPreview() = SpendooTheme {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.spendoo.designsystem.components.switch

import androidx.compose.material3.Switch as Material3Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import com.spendoo.designsystem.theme.theme.Theme
import androidx.compose.material3.Switch as Material3Switch

@Composable
fun Switch(
Expand All @@ -17,10 +18,10 @@ fun Switch(
onCheckedChange = onCheckedChange,
modifier = modifier,
colors = SwitchDefaults.colors(
checkedThumbColor = Theme.colorScheme.brand.primary,
checkedTrackColor = Theme.colorScheme.button.secondary,
checkedThumbColor = Theme.colorScheme.brand.onPrimary,
checkedTrackColor = Theme.colorScheme.icon.primary,
uncheckedThumbColor = Theme.colorScheme.text.body,
uncheckedTrackColor = Theme.colorScheme.background.secondary
uncheckedTrackColor = Color.Unspecified
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,29 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.semantics.Role

@Composable
fun Modifier.clickableNoRipple(
enabled: Boolean = true,
interactionSource: MutableInteractionSource? = MutableInteractionSource(),
interactionSource: MutableInteractionSource? = null,
indication: Indication? = null,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit,
) = this
.clickable(
): Modifier = composed {

val finalInteractionSource = remember(interactionSource) {
interactionSource ?: MutableInteractionSource()
}

this.clickable(
enabled = enabled,
interactionSource = remember { interactionSource },
interactionSource = finalInteractionSource,
indication = indication,
onClickLabel = onClickLabel,
role = role,
onClick = onClick
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,21 @@ private fun HomeContent(
isLoading = state.isBalanceLoading
)

item {
OffersSection(
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
offers = state.offers,
isLoading = state.isOffersLoading,
onViewAll = viewModel::onViewAllOffersClicked,
onOfferClicked = { link ->
link?.takeIf { it.isNotBlank() }?.let { url ->
val formattedUrl = if (url.startsWith("http://") || url.startsWith("https://")) url else "https://$url" //TODO: format url function
uriHandler.openUri(formattedUrl)
if (state.isHomeOffersEnabled) {
item {
OffersSection(
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
offers = state.offers,
isLoading = state.isOffersLoading,
onViewAll = viewModel::onViewAllOffersClicked,
onOfferClicked = { link ->
link?.takeIf { it.isNotBlank() }?.let { url ->
val formattedUrl = if (url.startsWith("http://") || url.startsWith("https://")) url else "https://$url" //TODO: format url function
uriHandler.openUri(formattedUrl)
}
}
}
)
)
}
}

item {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ data class HomeUiState(
val isGoalsLoading: Boolean = true,
val isTopSpendingLoading: Boolean = true,
val isNotificationsLoading: Boolean = true,
val isHomeOffersEnabled: Boolean = true,
val balanceSummary: BalanceSummaryUiState = BalanceSummaryUiState(),
val offers: List<OfferUiState> = emptyList(),
val goals: List<GoalUiState> = emptyList(),
Expand Down Expand Up @@ -62,6 +63,10 @@ data class OfferUiState(
val id: String,
val discountPercent: Int?,
val imageUrl: String?,
val title: String?,
val price: Double?,
val currency: String?,
val rating: Double?,
val link: String? = null
)

Expand All @@ -70,6 +75,10 @@ fun Offer.toUiState(): OfferUiState {
id = id,
discountPercent = discountPercent,
imageUrl = imageUrl,
title = title,
price = price,
currency = currency,
rating = rating,
link = link
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.spendoo.goals.domain.repository.GoalsRepository
import com.spendoo.home.api.NotificationsRoute
import com.spendoo.identity.api.ProfileRoute
import com.spendoo.identity.domain.repository.ProfileRepository
import com.spendoo.identity.domain.repository.SettingsRepository
import com.spendoo.notifications.domain.repository.NotificationRepository
import com.spendoo.offers.domain.repository.OffersRepository
import com.spendoo.shared.domain.utils.PageQuery
Expand All @@ -28,11 +29,13 @@ class HomeViewModel(
private val categoriesRepository: CategoriesRepository,
private val profileRepository: ProfileRepository,
private val notificationRepository: NotificationRepository,
private val goalsRepository: GoalsRepository
private val goalsRepository: GoalsRepository,
private val settingsRepository: SettingsRepository
) : BaseViewModel<HomeUiState>(HomeUiState()), HomeInteractionListener {

init {
listenToResetSignal()
observeHomeOffersEnabledSetting()
getHomeData()
}

Expand Down Expand Up @@ -199,7 +202,11 @@ class HomeViewModel(
onStart = { updateState { copy(isTopSpendingLoading = true) } },
onSuccess = { spending ->
updateState { copy(topSpending = spending.data.map { it.toUiState() }) }
loadOffers()
if (settingsRepository.isHomeOffersEnabled()) {
loadOffers()
} else {
updateState { copy(isOffersLoading = false) }
}
},
onError = { error ->
error.message?.let {
Expand Down Expand Up @@ -237,7 +244,9 @@ class HomeViewModel(
override fun onSpendingClicked(categoryId: String) {
navigate(CategoryOffersRoute(categoryId = categoryId))
}
override fun onViewAllOffersClicked() {}
override fun onViewAllOffersClicked() {
navigate(CategoryOffersRoute(categoryId = null))
}
override fun onViewAllGoalsClicked() {
navigate(GoalsRoute)
}
Expand All @@ -251,4 +260,21 @@ class HomeViewModel(
override fun onProfileClicked() {
navigate(ProfileRoute)
}

private fun observeHomeOffersEnabledSetting() {
tryToCollect(
block = { settingsRepository.observeHomeOffersEnabled() },
onEach = { isEnabled ->
updateState { copy(isHomeOffersEnabled = isEnabled) }
if (isEnabled) {
if (state.value.offers.isEmpty() && !state.value.isOffersLoading) {
loadOffers()
}
} else {
updateState { copy(offers = emptyList(), isOffersLoading = false) }
}
},
onError = {}
)
}
}
Loading
Loading