Merge branch 'dev'
This commit is contained in:
BIN
.github/repo_photos/banner.png
vendored
BIN
.github/repo_photos/banner.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 163 KiB |
BIN
.github/repo_photos/googleplay.png
vendored
Normal file
BIN
.github/repo_photos/googleplay.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
6
.github/workflows/android.yml
vendored
6
.github/workflows/android.yml
vendored
@@ -19,13 +19,13 @@ jobs:
|
||||
run: chmod +x gradlew
|
||||
|
||||
- name: Build debug APK with Gradle
|
||||
run: ./gradlew assembleDebug
|
||||
run: ./gradlew assembleFossDebug
|
||||
|
||||
- name: Run tests
|
||||
run: ./gradlew testDebugUnitTest
|
||||
run: ./gradlew testFossDebugUnitTest
|
||||
|
||||
- name: Upload debug APK artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: tomato-debug
|
||||
path: ./app/build/outputs/apk/debug/app-debug.apk
|
||||
|
||||
37
README.md
37
README.md
@@ -7,9 +7,6 @@ Tomato is a minimalist Pomodoro timer for Android based on Material 3 Expressive
|
||||
|
||||
</div>
|
||||
|
||||
> [!NOTE]
|
||||
> Tomato is available on Google Play in closed testing. Please [use this link to join the testers Google Group](https://groups.google.com/g/nsh07-app-testers) and then install [Tomato from the Play Store with this link](https://play.google.com/store/apps/details?id=org.nsh07.pomodoro). Tomato needs 12 testers for at least 14 days for the app to be available on Google Play publicly.
|
||||
|
||||
<div align="center">
|
||||
|
||||
<a href="https://hosted.weblate.org/engage/tomato/?utm_source=widget">
|
||||
@@ -24,15 +21,18 @@ Tomato is a minimalist Pomodoro timer for Android based on Material 3 Expressive
|
||||
<a href="https://github.com/nsh07/tomato/blob/main/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/nsh07/tomato?logo=gnu&color=blue&labelColor=1a1a1a">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/API-26+-blue?logo=android&labelColor=1a1a1a">
|
||||
<img src="https://img.shields.io/badge/API-27+-blue?logo=android&labelColor=1a1a1a">
|
||||
|
||||
<p>
|
||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.nsh07.pomodoro">
|
||||
<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="200">
|
||||
<a href="https://play.google.com/store/apps/details?id=org.nsh07.pomodoro">
|
||||
<img src=".github/repo_photos/googleplay.png" width="200">
|
||||
</a>
|
||||
<a href="https://f-droid.org/packages/org.nsh07.pomodoro">
|
||||
<img src="https://f-droid.org/badge/get-it-on.png" width="200">
|
||||
</a>
|
||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/org.nsh07.pomodoro">
|
||||
<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="200">
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
<a href="https://hosted.weblate.org/engage/tomato/">
|
||||
@@ -48,9 +48,11 @@ Tomato is a minimalist Pomodoro timer for Android based on Material 3 Expressive
|
||||
|
||||
<br/>
|
||||
|
||||
> *"... an app to support this habit helps me stay focused and get things done. Currently, that app is Tomato."*
|
||||
> *"... an app to support this habit helps me stay focused and get things done. Currently, that app
|
||||
is Tomato."*
|
||||
|
||||
\- [*Android Authority*](https://www.androidauthority.com/best-new-android-apps-october-2025-3602966/)
|
||||
\- [*Android
|
||||
Authority*](https://www.androidauthority.com/best-new-android-apps-october-2025-3602966/)
|
||||
|
||||
<br/>
|
||||
|
||||
@@ -92,23 +94,20 @@ translating this project into languages you know.
|
||||
|
||||
## Download
|
||||
|
||||
- **Google Play Store** (recommended): Tomato is available for closed testing on Google Play. Use
|
||||
[this link to join the testers Google Group](https://groups.google.com/g/nsh07-app-testers), then
|
||||
[install Tomato from the Play Store with this link](https://play.google.com/store/apps/details?id=org.nsh07.pomodoro) and help Tomato to get an official Play Store release.
|
||||
- **Google Play Store** (recommended): Tomato will soon be available (currently in closed testing)
|
||||
on the Google Play Store.
|
||||
[You can find it through this link](https://play.google.com/store/apps/details?id=org.nsh07.pomodoro).
|
||||
- **F-Droid** (recommended): Tomato is available on the official F-Droid repository. Simply open
|
||||
your preferred F-Droid app and search for Tomato.
|
||||
Updates on F-Droid are generally a week late. To get faster updates, you can install it through
|
||||
your preferred F-Droid app and search for Tomato. Updates on F-Droid are generally a week late. To
|
||||
get faster updates, you can install it through
|
||||
the [IzzyOnDroid repository](https://apt.izzysoft.de/fdroid/).
|
||||
- **Obtainium** (recommended): You can add this GitHub repository
|
||||
on [Obtainium](https://obtainium.imranr.dev/) to get updates directly from GitHub releases. This
|
||||
is the fastest way to install and update Tomato.
|
||||
- **GitHub releases**: Alternatively, you can manually download and install APKs from
|
||||
the [Releases](https://github.com/nsh07/Tomato/releases/latest) section of this repo (This
|
||||
method is not recommended, use Obtainium instead).
|
||||
the [Releases](https://github.com/nsh07/Tomato/releases/latest) section of this repo (This method
|
||||
is not recommended, use Google Play/F-Droid instead).
|
||||
|
||||
> [!TIP]
|
||||
> To [verify](https://developer.android.com/studio/command-line/apksigner#usage-verify) the APK
|
||||
> downloaded from Obtainium/GitHub, use the following signing certificate fingerprints:
|
||||
> downloaded from GitHub, use the following signing certificate fingerprints:
|
||||
> ```
|
||||
> SHA1: B1:4E:17:93:11:E8:DB:D5:35:EF:8D:E9:FB:8F:FF:08:F8:EC:65:08
|
||||
> SHA256: 07:BE:F3:05:81:BA:EE:8F:45:EC:93:E4:7E:E6:8E:F2:08:74:E5:0E:F5:70:9C:78:B2:EE:67:AC:86:BE:4C:3D
|
||||
|
||||
@@ -43,8 +43,8 @@ android {
|
||||
applicationId = "org.nsh07.pomodoro"
|
||||
minSdk = 27
|
||||
targetSdk = 36
|
||||
versionCode = 15
|
||||
versionName = "1.6.0"
|
||||
versionCode = 17
|
||||
versionName = "1.6.2"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
@@ -58,6 +58,19 @@ android {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions += "version"
|
||||
productFlavors {
|
||||
create("foss") {
|
||||
dimension = "version"
|
||||
isDefault = true
|
||||
}
|
||||
create("play") {
|
||||
dimension = "version"
|
||||
versionNameSuffix = "-play"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
@@ -103,6 +116,9 @@ dependencies {
|
||||
implementation(libs.androidx.room.ktx)
|
||||
ksp(libs.androidx.room.compiler)
|
||||
|
||||
"playImplementation"(libs.revenuecat.purchases)
|
||||
"playImplementation"(libs.revenuecat.purchases.ui)
|
||||
|
||||
testImplementation(libs.junit)
|
||||
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.billing
|
||||
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
/**
|
||||
* Google Play implementation of BillingManager
|
||||
*/
|
||||
class FossBillingManager : BillingManager {
|
||||
override val isPlus = MutableStateFlow(true).asStateFlow()
|
||||
override val isLoaded = MutableStateFlow(true).asStateFlow()
|
||||
}
|
||||
|
||||
object BillingManagerProvider {
|
||||
val manager: BillingManager = FossBillingManager()
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.billing
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.foundation.layout.padding
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalUriHandler
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.nsh07.pomodoro.R
|
||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||
|
||||
@Composable
|
||||
fun TomatoPlusPaywallDialog(
|
||||
isPlus: Boolean,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
|
||||
BackHandler(enabled = true, onDismiss)
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(colorScheme.surface)
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(
|
||||
painterResource(R.drawable.bmc),
|
||||
null,
|
||||
tint = colorScheme.onSurface
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Text(
|
||||
stringResource(R.string.tomato_foss),
|
||||
style = typography.headlineSmall,
|
||||
fontFamily = robotoFlexTopBar,
|
||||
color = colorScheme.onSurface
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
stringResource(R.string.tomato_foss_desc, "BuyMeACoffee"),
|
||||
textAlign = TextAlign.Center,
|
||||
color = colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(horizontal = 24.dp)
|
||||
)
|
||||
Spacer(Modifier.height(16.dp))
|
||||
Button(onClick = { uriHandler.openUri("https://coff.ee/nsh07") }) {
|
||||
Text("Buy Me A Coffee")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.billing
|
||||
|
||||
import android.content.Context
|
||||
|
||||
fun initializePurchases(context: Context) {}
|
||||
@@ -18,6 +18,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
|
||||
@@ -30,12 +30,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import org.nsh07.pomodoro.ui.AppScreen
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.SettingsViewModel
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoTheme
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel
|
||||
import org.nsh07.pomodoro.utils.toColor
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
private val timerViewModel: TimerViewModel by viewModels(factoryProducer = { TimerViewModel.Factory })
|
||||
private val settingsViewModel: SettingsViewModel by viewModels(factoryProducer = { SettingsViewModel.Factory })
|
||||
|
||||
private val appContainer by lazy {
|
||||
@@ -62,6 +60,20 @@ class MainActivity : ComponentActivity() {
|
||||
|
||||
val seed = preferencesState.colorScheme.toColor()
|
||||
|
||||
val isPlus by settingsViewModel.isPlus.collectAsStateWithLifecycle()
|
||||
val isPurchaseStateLoaded by settingsViewModel.isPurchaseStateLoaded.collectAsStateWithLifecycle()
|
||||
val isSettingsLoaded by settingsViewModel.isSettingsLoaded.collectAsStateWithLifecycle()
|
||||
|
||||
LaunchedEffect(isPurchaseStateLoaded, isPlus, isSettingsLoaded) {
|
||||
if (isPurchaseStateLoaded && isSettingsLoaded) {
|
||||
if (!isPlus) {
|
||||
settingsViewModel.resetPaywalledSettings()
|
||||
} else {
|
||||
settingsViewModel.reloadSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TomatoTheme(
|
||||
darkTheme = darkTheme,
|
||||
seedColor = seed,
|
||||
@@ -73,7 +85,7 @@ class MainActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
AppScreen(
|
||||
timerViewModel = timerViewModel,
|
||||
isPlus = isPlus,
|
||||
isAODEnabled = preferencesState.aodEnabled,
|
||||
setTimerFrequency = {
|
||||
appContainer.appTimerRepository.timerFrequency = it
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro
|
||||
|
||||
import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import org.nsh07.pomodoro.billing.initializePurchases
|
||||
import org.nsh07.pomodoro.data.AppContainer
|
||||
import org.nsh07.pomodoro.data.DefaultAppContainer
|
||||
|
||||
@@ -12,6 +30,8 @@ class TomatoApplication : Application() {
|
||||
super.onCreate()
|
||||
container = DefaultAppContainer(this)
|
||||
|
||||
initializePurchases(this)
|
||||
|
||||
val notificationChannel = NotificationChannel(
|
||||
"timer",
|
||||
getString(R.string.timer_progress),
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.billing
|
||||
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface BillingManager {
|
||||
val isPlus: StateFlow<Boolean>
|
||||
val isLoaded: StateFlow<Boolean>
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.nsh07.pomodoro.data
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -25,6 +26,8 @@ import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.nsh07.pomodoro.R
|
||||
import org.nsh07.pomodoro.billing.BillingManager
|
||||
import org.nsh07.pomodoro.billing.BillingManagerProvider
|
||||
import org.nsh07.pomodoro.service.addTimerActions
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
import org.nsh07.pomodoro.utils.millisecondsToStr
|
||||
@@ -33,7 +36,9 @@ interface AppContainer {
|
||||
val appPreferenceRepository: AppPreferenceRepository
|
||||
val appStatRepository: AppStatRepository
|
||||
val appTimerRepository: AppTimerRepository
|
||||
val billingManager: BillingManager
|
||||
val notificationManager: NotificationManagerCompat
|
||||
val notificationManagerService: NotificationManager
|
||||
val notificationBuilder: NotificationCompat.Builder
|
||||
val timerState: MutableStateFlow<TimerState>
|
||||
val time: MutableStateFlow<Long>
|
||||
@@ -52,10 +57,15 @@ class DefaultAppContainer(context: Context) : AppContainer {
|
||||
|
||||
override val appTimerRepository: AppTimerRepository by lazy { AppTimerRepository() }
|
||||
|
||||
override val billingManager: BillingManager by lazy { BillingManagerProvider.manager }
|
||||
|
||||
override val notificationManager: NotificationManagerCompat by lazy {
|
||||
NotificationManagerCompat.from(context)
|
||||
}
|
||||
|
||||
override val notificationManagerService: NotificationManager =
|
||||
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
override val notificationBuilder: NotificationCompat.Builder by lazy {
|
||||
NotificationCompat.Builder(context, "timer")
|
||||
.setSmallIcon(R.drawable.tomato_logo_notification)
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.data
|
||||
@@ -27,6 +37,7 @@ interface TimerRepository {
|
||||
|
||||
var alarmEnabled: Boolean
|
||||
var vibrateEnabled: Boolean
|
||||
var dndEnabled: Boolean
|
||||
|
||||
var colorScheme: ColorScheme
|
||||
|
||||
@@ -46,6 +57,7 @@ class AppTimerRepository : TimerRepository {
|
||||
override var timerFrequency: Float = 10f
|
||||
override var alarmEnabled = true
|
||||
override var vibrateEnabled = true
|
||||
override var dndEnabled: Boolean = false
|
||||
override var colorScheme = lightColorScheme()
|
||||
override var alarmSoundUri: Uri? =
|
||||
Settings.System.DEFAULT_ALARM_ALERT_URI ?: Settings.System.DEFAULT_RINGTONE_URI
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package org.nsh07.pomodoro.service
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.media.AudioAttributes
|
||||
@@ -30,7 +31,6 @@ import android.os.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
@@ -53,7 +53,8 @@ class TimerService : Service() {
|
||||
|
||||
private val timerRepository by lazy { appContainer.appTimerRepository }
|
||||
private val statRepository by lazy { appContainer.appStatRepository }
|
||||
private val notificationManager by lazy { NotificationManagerCompat.from(this) }
|
||||
private val notificationManager by lazy { appContainer.notificationManager }
|
||||
private val notificationManagerService by lazy { appContainer.notificationManagerService }
|
||||
private val notificationBuilder by lazy { appContainer.notificationBuilder }
|
||||
private val _timerState by lazy { appContainer.timerState }
|
||||
private val _time by lazy { appContainer.time }
|
||||
@@ -106,6 +107,7 @@ class TimerService : Service() {
|
||||
runBlocking {
|
||||
job.cancel()
|
||||
saveTimeToDb()
|
||||
setDoNotDisturb(false)
|
||||
notificationManager.cancel(1)
|
||||
alarm?.release()
|
||||
}
|
||||
@@ -127,7 +129,7 @@ class TimerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
Actions.SKIP.toString() -> skipTimer(true)
|
||||
Actions.SKIP.toString() -> skipScope.launch { skipTimer(true) }
|
||||
|
||||
Actions.STOP_ALARM.toString() -> stopAlarm()
|
||||
|
||||
@@ -140,6 +142,7 @@ class TimerService : Service() {
|
||||
updateProgressSegments()
|
||||
|
||||
if (timerState.value.timerRunning) {
|
||||
setDoNotDisturb(false)
|
||||
notificationBuilder.clearActions().addTimerActions(
|
||||
this, R.drawable.play, getString(R.string.start)
|
||||
)
|
||||
@@ -149,6 +152,8 @@ class TimerService : Service() {
|
||||
}
|
||||
pauseTime = SystemClock.elapsedRealtime()
|
||||
} else {
|
||||
if (timerState.value.timerMode == TimerMode.FOCUS) setDoNotDisturb(true)
|
||||
else setDoNotDisturb(false)
|
||||
notificationBuilder.clearActions().addTimerActions(
|
||||
this, R.drawable.pause, getString(R.string.stop)
|
||||
)
|
||||
@@ -324,48 +329,48 @@ class TimerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun skipTimer(fromButton: Boolean = false) {
|
||||
private suspend fun skipTimer(fromButton: Boolean = false) {
|
||||
updateProgressSegments()
|
||||
skipScope.launch {
|
||||
saveTimeToDb()
|
||||
updateProgressSegments()
|
||||
showTimerNotification(0, paused = true, complete = !fromButton)
|
||||
startTime = 0L
|
||||
pauseTime = 0L
|
||||
pauseDuration = 0L
|
||||
saveTimeToDb()
|
||||
updateProgressSegments()
|
||||
showTimerNotification(0, paused = true, complete = !fromButton)
|
||||
startTime = 0L
|
||||
pauseTime = 0L
|
||||
pauseDuration = 0L
|
||||
|
||||
cycles = (cycles + 1) % (timerRepository.sessionLength * 2)
|
||||
cycles = (cycles + 1) % (timerRepository.sessionLength * 2)
|
||||
|
||||
if (cycles % 2 == 0) {
|
||||
time = timerRepository.focusTime
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(
|
||||
timerMode = TimerMode.FOCUS,
|
||||
timeStr = millisecondsToStr(time),
|
||||
totalTime = time,
|
||||
nextTimerMode = if (cycles == (timerRepository.sessionLength - 1) * 2) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
|
||||
nextTimeStr = if (cycles == (timerRepository.sessionLength - 1) * 2) millisecondsToStr(
|
||||
timerRepository.longBreakTime
|
||||
) else millisecondsToStr(
|
||||
timerRepository.shortBreakTime
|
||||
),
|
||||
currentFocusCount = cycles / 2 + 1,
|
||||
totalFocusCount = timerRepository.sessionLength
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val long = cycles == (timerRepository.sessionLength * 2) - 1
|
||||
time = if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime
|
||||
if (cycles % 2 == 0) {
|
||||
if (timerState.value.timerRunning) setDoNotDisturb(true)
|
||||
time = timerRepository.focusTime
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(
|
||||
timerMode = TimerMode.FOCUS,
|
||||
timeStr = millisecondsToStr(time),
|
||||
totalTime = time,
|
||||
nextTimerMode = if (cycles == (timerRepository.sessionLength - 1) * 2) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
|
||||
nextTimeStr = if (cycles == (timerRepository.sessionLength - 1) * 2) millisecondsToStr(
|
||||
timerRepository.longBreakTime
|
||||
) else millisecondsToStr(
|
||||
timerRepository.shortBreakTime
|
||||
),
|
||||
currentFocusCount = cycles / 2 + 1,
|
||||
totalFocusCount = timerRepository.sessionLength
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (timerState.value.timerRunning) setDoNotDisturb(false)
|
||||
val long = cycles == (timerRepository.sessionLength * 2) - 1
|
||||
time = if (long) timerRepository.longBreakTime else timerRepository.shortBreakTime
|
||||
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(
|
||||
timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
|
||||
timeStr = millisecondsToStr(time),
|
||||
totalTime = time,
|
||||
nextTimerMode = TimerMode.FOCUS,
|
||||
nextTimeStr = millisecondsToStr(timerRepository.focusTime)
|
||||
)
|
||||
}
|
||||
_timerState.update { currentState ->
|
||||
currentState.copy(
|
||||
timerMode = if (long) TimerMode.LONG_BREAK else TimerMode.SHORT_BREAK,
|
||||
timeStr = millisecondsToStr(time),
|
||||
totalTime = time,
|
||||
nextTimerMode = TimerMode.FOCUS,
|
||||
nextTimeStr = millisecondsToStr(timerRepository.focusTime)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,7 +446,15 @@ class TimerService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAlarmTone() {
|
||||
private fun setDoNotDisturb(doNotDisturb: Boolean) {
|
||||
if (timerRepository.dndEnabled && notificationManagerService.isNotificationPolicyAccessGranted()) {
|
||||
if (doNotDisturb) {
|
||||
notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALARMS)
|
||||
} else notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAlarmTone() {
|
||||
alarm?.release()
|
||||
alarm = initializeMediaPlayer()
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
@@ -41,8 +43,10 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
@@ -54,6 +58,7 @@ import androidx.navigation3.runtime.entryProvider
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import androidx.window.core.layout.WindowSizeClass
|
||||
import org.nsh07.pomodoro.billing.TomatoPlusPaywallDialog
|
||||
import org.nsh07.pomodoro.service.TimerService
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.SettingsScreenRoot
|
||||
import org.nsh07.pomodoro.ui.statsScreen.StatsScreenRoot
|
||||
@@ -65,10 +70,11 @@ import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerViewModel
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun AppScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
timerViewModel: TimerViewModel = viewModel(factory = TimerViewModel.Factory),
|
||||
isAODEnabled: Boolean,
|
||||
setTimerFrequency: (Float) -> Unit
|
||||
isPlus: Boolean,
|
||||
setTimerFrequency: (Float) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
timerViewModel: TimerViewModel = viewModel(factory = TimerViewModel.Factory)
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -91,6 +97,7 @@ fun AppScreen(
|
||||
}
|
||||
}
|
||||
|
||||
var showPaywall by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
@@ -157,6 +164,7 @@ fun AppScreen(
|
||||
entry<Screen.Timer> {
|
||||
TimerScreen(
|
||||
timerState = uiState,
|
||||
isPlus = isPlus,
|
||||
progress = { progress },
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
@@ -218,6 +226,7 @@ fun AppScreen(
|
||||
|
||||
entry<Screen.Settings.Main> {
|
||||
SettingsScreenRoot(
|
||||
setShowPaywall = { showPaywall = it },
|
||||
modifier = modifier.padding(
|
||||
start = contentPadding.calculateStartPadding(layoutDirection),
|
||||
end = contentPadding.calculateEndPadding(layoutDirection),
|
||||
@@ -240,4 +249,12 @@ fun AppScreen(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
showPaywall,
|
||||
enter = slideInVertically { it },
|
||||
exit = slideOutVertically { it }
|
||||
) {
|
||||
TomatoPlusPaywallDialog(isPlus = isPlus) { showPaywall = false }
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.compose.animation.fadeIn
|
||||
@@ -67,6 +68,7 @@ import org.nsh07.pomodoro.service.TimerService
|
||||
import org.nsh07.pomodoro.ui.Screen
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.components.AboutCard
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.components.ClickableListItem
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusPromo
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.screens.AlarmSettings
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.screens.AppearanceSettings
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.screens.TimerSettings
|
||||
@@ -80,6 +82,7 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.topBarColors
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreenRoot(
|
||||
setShowPaywall: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SettingsViewModel = viewModel(factory = SettingsViewModel.Factory)
|
||||
) {
|
||||
@@ -102,8 +105,10 @@ fun SettingsScreenRoot(
|
||||
viewModel.longBreakTimeTextFieldState
|
||||
}
|
||||
|
||||
val isPlus by viewModel.isPlus.collectAsStateWithLifecycle()
|
||||
val alarmEnabled by viewModel.alarmEnabled.collectAsStateWithLifecycle(true)
|
||||
val vibrateEnabled by viewModel.vibrateEnabled.collectAsStateWithLifecycle(true)
|
||||
val dndEnabled by viewModel.dndEnabled.collectAsStateWithLifecycle(false)
|
||||
val alarmSound by viewModel.alarmSound.collectAsStateWithLifecycle(viewModel.currentAlarmSound)
|
||||
|
||||
val preferencesState by viewModel.preferencesState.collectAsStateWithLifecycle()
|
||||
@@ -118,6 +123,7 @@ fun SettingsScreenRoot(
|
||||
}
|
||||
|
||||
SettingsScreen(
|
||||
isPlus = isPlus,
|
||||
preferencesState = preferencesState,
|
||||
backStack = backStack,
|
||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||
@@ -126,11 +132,13 @@ fun SettingsScreenRoot(
|
||||
sessionsSliderState = sessionsSliderState,
|
||||
alarmEnabled = alarmEnabled,
|
||||
vibrateEnabled = vibrateEnabled,
|
||||
dndEnabled = dndEnabled,
|
||||
alarmSound = alarmSound,
|
||||
onAlarmEnabledChange = viewModel::saveAlarmEnabled,
|
||||
onVibrateEnabledChange = viewModel::saveVibrateEnabled,
|
||||
onBlackThemeChange = viewModel::saveBlackTheme,
|
||||
onAodEnabledChange = viewModel::saveAodEnabled,
|
||||
onDndEnabledChange = viewModel::saveDndEnabled,
|
||||
onAlarmSoundChanged = {
|
||||
viewModel.saveAlarmSound(it)
|
||||
Intent(context, TimerService::class.java).apply {
|
||||
@@ -140,13 +148,16 @@ fun SettingsScreenRoot(
|
||||
},
|
||||
onThemeChange = viewModel::saveTheme,
|
||||
onColorSchemeChange = viewModel::saveColorScheme,
|
||||
setShowPaywall = setShowPaywall,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("LocalContextGetResourceValueCall")
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
private fun SettingsScreen(
|
||||
isPlus: Boolean,
|
||||
preferencesState: PreferencesState,
|
||||
backStack: SnapshotStateList<Screen.Settings>,
|
||||
focusTimeInputFieldState: TextFieldState,
|
||||
@@ -155,14 +166,17 @@ private fun SettingsScreen(
|
||||
sessionsSliderState: SliderState,
|
||||
alarmEnabled: Boolean,
|
||||
vibrateEnabled: Boolean,
|
||||
dndEnabled: Boolean,
|
||||
alarmSound: String,
|
||||
onAlarmEnabledChange: (Boolean) -> Unit,
|
||||
onVibrateEnabledChange: (Boolean) -> Unit,
|
||||
onBlackThemeChange: (Boolean) -> Unit,
|
||||
onAodEnabledChange: (Boolean) -> Unit,
|
||||
onDndEnabledChange: (Boolean) -> Unit,
|
||||
onAlarmSoundChanged: (Uri?) -> Unit,
|
||||
onThemeChange: (String) -> Unit,
|
||||
onColorSchemeChange: (Color) -> Unit,
|
||||
setShowPaywall: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -212,10 +226,20 @@ private fun SettingsScreen(
|
||||
) {
|
||||
item { Spacer(Modifier.height(12.dp)) }
|
||||
|
||||
item { AboutCard() }
|
||||
if (!isPlus) item {
|
||||
PlusPromo(isPlus, setShowPaywall)
|
||||
Spacer(Modifier.height(14.dp))
|
||||
}
|
||||
|
||||
item { AboutCard(isPlus) }
|
||||
|
||||
item { Spacer(Modifier.height(12.dp)) }
|
||||
|
||||
if (isPlus) item {
|
||||
PlusPromo(isPlus, setShowPaywall)
|
||||
Spacer(Modifier.height(14.dp))
|
||||
}
|
||||
|
||||
itemsIndexed(settingsScreens) { index, item ->
|
||||
ClickableListItem(
|
||||
leadingContent = {
|
||||
@@ -261,21 +285,27 @@ private fun SettingsScreen(
|
||||
entry<Screen.Settings.Appearance> {
|
||||
AppearanceSettings(
|
||||
preferencesState = preferencesState,
|
||||
isPlus = isPlus,
|
||||
onBlackThemeChange = onBlackThemeChange,
|
||||
onThemeChange = onThemeChange,
|
||||
onColorSchemeChange = onColorSchemeChange,
|
||||
setShowPaywall = setShowPaywall,
|
||||
onBack = backStack::removeLastOrNull
|
||||
)
|
||||
}
|
||||
entry<Screen.Settings.Timer> {
|
||||
TimerSettings(
|
||||
isPlus = isPlus,
|
||||
aodEnabled = preferencesState.aodEnabled,
|
||||
dndEnabled = dndEnabled,
|
||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
||||
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
|
||||
sessionsSliderState = sessionsSliderState,
|
||||
onAodEnabledChange = onAodEnabledChange,
|
||||
onBack = backStack::removeLastOrNull
|
||||
onDndEnabledChange = onDndEnabledChange,
|
||||
setShowPaywall = setShowPaywall,
|
||||
onBack = backStack::removeLastOrNull,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.Button
|
||||
@@ -50,7 +51,10 @@ import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||
|
||||
// Taken from https://github.com/shub39/Grit/blob/master/app/src/main/java/com/shub39/grit/core/presentation/settings/ui/component/AboutApp.kt
|
||||
@Composable
|
||||
fun AboutCard(modifier: Modifier = Modifier) {
|
||||
fun AboutCard(
|
||||
isPlus: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val uriHandler = LocalUriHandler.current
|
||||
val context = LocalContext.current
|
||||
|
||||
@@ -76,7 +80,8 @@ fun AboutCard(modifier: Modifier = Modifier) {
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.app_name),
|
||||
if (!isPlus) stringResource(R.string.app_name)
|
||||
else stringResource(R.string.app_name_plus),
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontFamily = robotoFlexTopBar
|
||||
)
|
||||
@@ -123,8 +128,9 @@ fun AboutCard(modifier: Modifier = Modifier) {
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.coffee),
|
||||
painterResource(R.drawable.bmc),
|
||||
contentDescription = "Buy me a coffee",
|
||||
modifier = Modifier.height(24.dp)
|
||||
)
|
||||
|
||||
Text(text = "Buy me a coffee")
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen.components
|
||||
|
||||
import android.os.Build
|
||||
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.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -28,6 +28,8 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CornerSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
@@ -38,6 +40,7 @@ import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -56,6 +59,7 @@ fun ColorSchemePickerListItem(
|
||||
color: Color,
|
||||
items: Int,
|
||||
index: Int,
|
||||
isPlus: Boolean,
|
||||
onColorChange: (Color) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
@@ -65,6 +69,7 @@ fun ColorSchemePickerListItem(
|
||||
Color(0xff9fd75c), Color(0xffc1d02d), Color(0xfffabd00), Color(0xffffb86e),
|
||||
Color.White
|
||||
)
|
||||
val zeroCorner = remember { CornerSize(0) }
|
||||
|
||||
Column(
|
||||
modifier
|
||||
@@ -76,45 +81,49 @@ fun ColorSchemePickerListItem(
|
||||
}
|
||||
)
|
||||
) {
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
painterResource(R.drawable.colors),
|
||||
null
|
||||
)
|
||||
},
|
||||
headlineContent = { Text("Dynamic color") },
|
||||
supportingContent = { Text("Adapt theme colors from your wallpaper") },
|
||||
trailingContent = {
|
||||
val checked = color == colorSchemes.last()
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
if (it) onColorChange(colorSchemes.last())
|
||||
else onColorChange(colorSchemes.first())
|
||||
},
|
||||
thumbContent = {
|
||||
if (checked) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.check),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(SwitchDefaults.IconSize),
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.clear),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(SwitchDefaults.IconSize),
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = switchColors
|
||||
)
|
||||
},
|
||||
colors = listItemColors,
|
||||
modifier = Modifier.clip(middleListItemShape)
|
||||
)
|
||||
Spacer(Modifier.height(2.dp))
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
painterResource(R.drawable.colors),
|
||||
null
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(stringResource(R.string.dynamic_color)) },
|
||||
supportingContent = { Text(stringResource(R.string.dynamic_color_desc)) },
|
||||
trailingContent = {
|
||||
val checked = color == colorSchemes.last()
|
||||
Switch(
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
if (it) onColorChange(colorSchemes.last())
|
||||
else onColorChange(colorSchemes.first())
|
||||
},
|
||||
enabled = isPlus,
|
||||
thumbContent = {
|
||||
if (checked) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.check),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(SwitchDefaults.IconSize),
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.clear),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(SwitchDefaults.IconSize),
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = switchColors
|
||||
)
|
||||
},
|
||||
colors = listItemColors,
|
||||
modifier = Modifier.clip(middleListItemShape)
|
||||
)
|
||||
Spacer(Modifier.height(2.dp))
|
||||
}
|
||||
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
@@ -131,24 +140,31 @@ fun ColorSchemePickerListItem(
|
||||
)
|
||||
},
|
||||
colors = listItemColors,
|
||||
modifier = Modifier.clip(middleListItemShape)
|
||||
modifier = Modifier.clip(
|
||||
RoundedCornerShape(
|
||||
topStart = middleListItemShape.topStart,
|
||||
topEnd = middleListItemShape.topEnd,
|
||||
zeroCorner,
|
||||
zeroCorner
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
LazyRow(
|
||||
contentPadding = PaddingValues(horizontal = 48.dp),
|
||||
userScrollEnabled = isPlus,
|
||||
modifier = Modifier
|
||||
.background(listItemColors.containerColor)
|
||||
.padding(bottom = 8.dp)
|
||||
) {
|
||||
LazyRow(contentPadding = PaddingValues(horizontal = 48.dp)) {
|
||||
items(colorSchemes.dropLast(1)) {
|
||||
ColorPickerButton(
|
||||
it,
|
||||
it == color,
|
||||
modifier = Modifier.padding(4.dp)
|
||||
) {
|
||||
onColorChange(it)
|
||||
}
|
||||
items(colorSchemes.dropLast(1)) {
|
||||
ColorPickerButton(
|
||||
color = it,
|
||||
isSelected = it == color,
|
||||
enabled = isPlus,
|
||||
modifier = Modifier.padding(4.dp)
|
||||
) {
|
||||
onColorChange(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,12 +176,17 @@ fun ColorSchemePickerListItem(
|
||||
fun ColorPickerButton(
|
||||
color: Color,
|
||||
isSelected: Boolean,
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
IconButton(
|
||||
shapes = IconButtonDefaults.shapes(),
|
||||
colors = IconButtonDefaults.iconButtonColors(containerColor = color),
|
||||
colors = IconButtonDefaults.iconButtonColors(
|
||||
containerColor = color,
|
||||
disabledContainerColor = color.copy(0.3f)
|
||||
),
|
||||
enabled = enabled,
|
||||
modifier = modifier.size(48.dp),
|
||||
onClick = onClick
|
||||
) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun PlusDivider(
|
||||
setShowPaywall: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center, modifier = modifier.padding(vertical = 14.dp)) {
|
||||
HorizontalDivider(modifier = Modifier.clip(CircleShape), thickness = 4.dp)
|
||||
Button(
|
||||
onClick = { setShowPaywall(true) },
|
||||
modifier = Modifier
|
||||
.background(colorScheme.surfaceContainer)
|
||||
.padding(horizontal = 8.dp)
|
||||
) {
|
||||
Text("Customize further with Tomato+", style = typography.titleSmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
import androidx.compose.material3.MaterialTheme.typography
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.nsh07.pomodoro.R
|
||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||
|
||||
@Composable
|
||||
fun PlusPromo(
|
||||
isPlus: Boolean,
|
||||
setShowPaywall: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val container = if (isPlus) colorScheme.surfaceBright else colorScheme.primary
|
||||
val onContainer = if (isPlus) colorScheme.onSurface else colorScheme.onPrimary
|
||||
val onContainerVariant = if (isPlus) colorScheme.onSurfaceVariant else colorScheme.onPrimary
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier
|
||||
.clip(CircleShape)
|
||||
.background(container)
|
||||
.padding(16.dp)
|
||||
.clickable { setShowPaywall(true) }
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.tomato_logo_notification),
|
||||
null,
|
||||
tint = onContainerVariant,
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(
|
||||
if (!isPlus) stringResource(R.string.get_plus)
|
||||
else stringResource(R.string.app_name_plus),
|
||||
style = typography.titleLarge,
|
||||
fontFamily = robotoFlexTopBar,
|
||||
color = onContainer
|
||||
)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Icon(
|
||||
painterResource(R.drawable.arrow_forward_big),
|
||||
null,
|
||||
tint = onContainerVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,7 @@ import androidx.compose.ui.unit.dp
|
||||
import org.nsh07.pomodoro.R
|
||||
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.bottomListItemShape
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.cardShape
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
|
||||
|
||||
@@ -69,11 +70,13 @@ fun ThemePickerListItem(
|
||||
Column(
|
||||
modifier
|
||||
.clip(
|
||||
when (index) {
|
||||
0 -> topListItemShape
|
||||
items - 1 -> bottomListItemShape
|
||||
else -> middleListItemShape
|
||||
},
|
||||
if (items > 1)
|
||||
when (index) {
|
||||
0 -> topListItemShape
|
||||
items - 1 -> bottomListItemShape
|
||||
else -> middleListItemShape
|
||||
}
|
||||
else cardShape,
|
||||
),
|
||||
) {
|
||||
ListItem(
|
||||
|
||||
@@ -48,6 +48,7 @@ import androidx.compose.ui.unit.dp
|
||||
import org.nsh07.pomodoro.R
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.components.ColorSchemePickerListItem
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.components.ThemePickerListItem
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.viewModel.PreferencesState
|
||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||
@@ -55,16 +56,18 @@ import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
||||
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
|
||||
import org.nsh07.pomodoro.ui.theme.CustomColors.topBarColors
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.bottomListItemShape
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoTheme
|
||||
import org.nsh07.pomodoro.utils.toColor
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun AppearanceSettings(
|
||||
preferencesState: PreferencesState,
|
||||
isPlus: Boolean,
|
||||
onBlackThemeChange: (Boolean) -> Unit,
|
||||
onThemeChange: (String) -> Unit,
|
||||
onColorSchemeChange: (Color) -> Unit,
|
||||
setShowPaywall: (Boolean) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
@@ -100,22 +103,26 @@ fun AppearanceSettings(
|
||||
item {
|
||||
Spacer(Modifier.height(14.dp))
|
||||
}
|
||||
item {
|
||||
ColorSchemePickerListItem(
|
||||
color = preferencesState.colorScheme.toColor(),
|
||||
items = 3,
|
||||
index = 0,
|
||||
onColorChange = onColorSchemeChange
|
||||
)
|
||||
}
|
||||
item {
|
||||
ThemePickerListItem(
|
||||
theme = preferencesState.theme,
|
||||
onThemeChange = onThemeChange,
|
||||
items = if (isPlus) 3 else 1,
|
||||
index = 0
|
||||
)
|
||||
}
|
||||
|
||||
if (!isPlus) {
|
||||
item { PlusDivider(setShowPaywall) }
|
||||
}
|
||||
|
||||
item {
|
||||
ColorSchemePickerListItem(
|
||||
color = preferencesState.colorScheme.toColor(),
|
||||
items = 3,
|
||||
index = 1,
|
||||
modifier = Modifier
|
||||
.clip(middleListItemShape)
|
||||
index = if (isPlus) 1 else 0,
|
||||
isPlus = isPlus,
|
||||
onColorChange = onColorSchemeChange,
|
||||
)
|
||||
}
|
||||
item {
|
||||
@@ -136,6 +143,7 @@ fun AppearanceSettings(
|
||||
Switch(
|
||||
checked = item.checked,
|
||||
onCheckedChange = { item.onClick(it) },
|
||||
enabled = isPlus,
|
||||
thumbContent = {
|
||||
if (item.checked) {
|
||||
Icon(
|
||||
@@ -168,11 +176,15 @@ fun AppearanceSettings(
|
||||
@Composable
|
||||
fun AppearanceSettingsPreview() {
|
||||
val preferencesState = PreferencesState()
|
||||
AppearanceSettings(
|
||||
preferencesState = preferencesState,
|
||||
onBlackThemeChange = {},
|
||||
onThemeChange = {},
|
||||
onColorSchemeChange = {},
|
||||
onBack = {}
|
||||
)
|
||||
TomatoTheme(dynamicColor = false) {
|
||||
AppearanceSettings(
|
||||
preferencesState = preferencesState,
|
||||
isPlus = false,
|
||||
onBlackThemeChange = {},
|
||||
onThemeChange = {},
|
||||
onColorSchemeChange = {},
|
||||
setShowPaywall = {},
|
||||
onBack = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
|
||||
package org.nsh07.pomodoro.ui.settingsScreen.screens
|
||||
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
@@ -31,6 +36,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.input.TextFieldState
|
||||
@@ -51,6 +57,7 @@ import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
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
|
||||
@@ -59,6 +66,7 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
@@ -67,6 +75,7 @@ import androidx.compose.ui.unit.dp
|
||||
import org.nsh07.pomodoro.R
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.SettingsSwitchItem
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.components.MinuteInputField
|
||||
import org.nsh07.pomodoro.ui.settingsScreen.components.PlusDivider
|
||||
import org.nsh07.pomodoro.ui.theme.AppFonts.robotoFlexTopBar
|
||||
import org.nsh07.pomodoro.ui.theme.CustomColors.listItemColors
|
||||
import org.nsh07.pomodoro.ui.theme.CustomColors.switchColors
|
||||
@@ -76,19 +85,60 @@ import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.cardShape
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.middleListItemShape
|
||||
import org.nsh07.pomodoro.ui.theme.TomatoShapeDefaults.topListItemShape
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun TimerSettings(
|
||||
isPlus: Boolean,
|
||||
aodEnabled: Boolean,
|
||||
dndEnabled: Boolean,
|
||||
focusTimeInputFieldState: TextFieldState,
|
||||
shortBreakTimeInputFieldState: TextFieldState,
|
||||
longBreakTimeInputFieldState: TextFieldState,
|
||||
sessionsSliderState: SliderState,
|
||||
onAodEnabledChange: (Boolean) -> Unit,
|
||||
onDndEnabledChange: (Boolean) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
setShowPaywall: (Boolean) -> Unit
|
||||
) {
|
||||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior()
|
||||
val context = LocalContext.current
|
||||
val appName = stringResource(R.string.app_name)
|
||||
val notificationManagerService =
|
||||
remember { context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
if (!notificationManagerService.isNotificationPolicyAccessGranted())
|
||||
onDndEnabledChange(false)
|
||||
}
|
||||
|
||||
val switchItems = listOf(
|
||||
SettingsSwitchItem(
|
||||
checked = dndEnabled,
|
||||
icon = R.drawable.dnd,
|
||||
label = R.string.dnd,
|
||||
description = R.string.dnd_desc,
|
||||
onClick = {
|
||||
if (it && !notificationManagerService.isNotificationPolicyAccessGranted()) {
|
||||
val intent = Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)
|
||||
Toast.makeText(context, "Enable permission for \"$appName\"", Toast.LENGTH_LONG)
|
||||
.show()
|
||||
context.startActivity(intent)
|
||||
} else if (!it && notificationManagerService.isNotificationPolicyAccessGranted()) {
|
||||
notificationManagerService.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL)
|
||||
}
|
||||
onDndEnabledChange(it)
|
||||
}
|
||||
),
|
||||
SettingsSwitchItem(
|
||||
checked = aodEnabled,
|
||||
icon = R.drawable.aod,
|
||||
label = R.string.always_on_display,
|
||||
description = R.string.always_on_display_desc,
|
||||
onClick = onAodEnabledChange
|
||||
)
|
||||
)
|
||||
|
||||
Column(modifier.nestedScroll(scrollBehavior.nestedScrollConnection)) {
|
||||
LargeFlexibleTopAppBar(
|
||||
@@ -213,14 +263,8 @@ fun TimerSettings(
|
||||
)
|
||||
}
|
||||
item { Spacer(Modifier.height(12.dp)) }
|
||||
item {
|
||||
val item = SettingsSwitchItem(
|
||||
checked = aodEnabled,
|
||||
icon = R.drawable.aod,
|
||||
label = R.string.always_on_display,
|
||||
description = R.string.always_on_display_desc,
|
||||
onClick = onAodEnabledChange
|
||||
)
|
||||
|
||||
itemsIndexed(if (isPlus) switchItems else switchItems.take(1)) { index, item ->
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
@@ -254,10 +298,61 @@ fun TimerSettings(
|
||||
)
|
||||
},
|
||||
colors = listItemColors,
|
||||
modifier = Modifier.clip(cardShape)
|
||||
modifier = Modifier.clip(
|
||||
if (isPlus) when (index) {
|
||||
0 -> topListItemShape
|
||||
switchItems.size - 1 -> bottomListItemShape
|
||||
else -> middleListItemShape
|
||||
}
|
||||
else cardShape
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (!isPlus) {
|
||||
item {
|
||||
PlusDivider(setShowPaywall)
|
||||
}
|
||||
itemsIndexed(switchItems.drop(1)) { index, item ->
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
painterResource(item.icon),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(top = 4.dp)
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(stringResource(item.label)) },
|
||||
supportingContent = { Text(stringResource(item.description)) },
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = item.checked,
|
||||
onCheckedChange = { item.onClick(it) },
|
||||
enabled = isPlus,
|
||||
thumbContent = {
|
||||
if (item.checked) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.check),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(SwitchDefaults.IconSize),
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.clear),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(SwitchDefaults.IconSize),
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = switchColors
|
||||
)
|
||||
},
|
||||
colors = listItemColors,
|
||||
modifier = Modifier.clip(cardShape)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
Column(
|
||||
@@ -306,12 +401,16 @@ private fun TimerSettingsPreview() {
|
||||
steps = 6
|
||||
)
|
||||
TimerSettings(
|
||||
isPlus = false,
|
||||
aodEnabled = true,
|
||||
dndEnabled = false,
|
||||
focusTimeInputFieldState = focusTimeInputFieldState,
|
||||
shortBreakTimeInputFieldState = shortBreakTimeInputFieldState,
|
||||
longBreakTimeInputFieldState = longBreakTimeInputFieldState,
|
||||
sessionsSliderState = sessionsSliderState,
|
||||
aodEnabled = true,
|
||||
onBack = {},
|
||||
onAodEnabledChange = {}
|
||||
onAodEnabledChange = {},
|
||||
onDndEnabledChange = {},
|
||||
setShowPaywall = {},
|
||||
onBack = {}
|
||||
)
|
||||
}
|
||||
@@ -40,17 +40,25 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.nsh07.pomodoro.TomatoApplication
|
||||
import org.nsh07.pomodoro.billing.BillingManager
|
||||
import org.nsh07.pomodoro.data.AppPreferenceRepository
|
||||
import org.nsh07.pomodoro.data.TimerRepository
|
||||
import org.nsh07.pomodoro.ui.Screen
|
||||
|
||||
@OptIn(FlowPreview::class, ExperimentalMaterial3Api::class)
|
||||
class SettingsViewModel(
|
||||
private val billingManager: BillingManager,
|
||||
private val preferenceRepository: AppPreferenceRepository,
|
||||
private val timerRepository: TimerRepository,
|
||||
) : ViewModel() {
|
||||
val backStack = mutableStateListOf<Screen.Settings>(Screen.Settings.Main)
|
||||
|
||||
val isPlus = billingManager.isPlus
|
||||
val isPurchaseStateLoaded = billingManager.isLoaded
|
||||
|
||||
private val _isSettingsLoaded = MutableStateFlow(false)
|
||||
val isSettingsLoaded = _isSettingsLoaded.asStateFlow()
|
||||
|
||||
private val _preferencesState = MutableStateFlow(PreferencesState())
|
||||
val preferencesState = _preferencesState.asStateFlow()
|
||||
|
||||
@@ -85,26 +93,13 @@ class SettingsViewModel(
|
||||
preferenceRepository.getBooleanPreferenceFlow("alarm_enabled").distinctUntilChanged()
|
||||
val vibrateEnabled =
|
||||
preferenceRepository.getBooleanPreferenceFlow("vibrate_enabled").distinctUntilChanged()
|
||||
val dndEnabled =
|
||||
preferenceRepository.getBooleanPreferenceFlow("dnd_enabled").distinctUntilChanged()
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
val theme = preferenceRepository.getStringPreference("theme")
|
||||
?: preferenceRepository.saveStringPreference("theme", "auto")
|
||||
val colorScheme = preferenceRepository.getStringPreference("color_scheme")
|
||||
?: preferenceRepository.saveStringPreference("color_scheme", Color.White.toString())
|
||||
val blackTheme = preferenceRepository.getBooleanPreference("black_theme")
|
||||
?: preferenceRepository.saveBooleanPreference("black_theme", false)
|
||||
val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
|
||||
|
||||
_preferencesState.update { currentState ->
|
||||
currentState.copy(
|
||||
theme = theme,
|
||||
colorScheme = colorScheme,
|
||||
blackTheme = blackTheme,
|
||||
aodEnabled = aodEnabled
|
||||
)
|
||||
}
|
||||
reloadSettings()
|
||||
_isSettingsLoaded.value = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +174,13 @@ class SettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun saveDndEnabled(enabled: Boolean) {
|
||||
viewModelScope.launch {
|
||||
timerRepository.dndEnabled = enabled
|
||||
preferenceRepository.saveBooleanPreference("dnd_enabled", enabled)
|
||||
}
|
||||
}
|
||||
|
||||
fun saveAlarmSound(uri: Uri?) {
|
||||
viewModelScope.launch {
|
||||
timerRepository.alarmSoundUri = uri
|
||||
@@ -222,14 +224,46 @@ class SettingsViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun resetPaywalledSettings() {
|
||||
_preferencesState.update { currentState ->
|
||||
currentState.copy(
|
||||
aodEnabled = false,
|
||||
blackTheme = false,
|
||||
colorScheme = Color.White.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun reloadSettings() {
|
||||
val theme = preferenceRepository.getStringPreference("theme")
|
||||
?: preferenceRepository.saveStringPreference("theme", "auto")
|
||||
val colorScheme = preferenceRepository.getStringPreference("color_scheme")
|
||||
?: preferenceRepository.saveStringPreference("color_scheme", Color.White.toString())
|
||||
val blackTheme = preferenceRepository.getBooleanPreference("black_theme")
|
||||
?: preferenceRepository.saveBooleanPreference("black_theme", false)
|
||||
val aodEnabled = preferenceRepository.getBooleanPreference("aod_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("aod_enabled", false)
|
||||
|
||||
_preferencesState.update { currentState ->
|
||||
currentState.copy(
|
||||
theme = theme,
|
||||
colorScheme = colorScheme,
|
||||
blackTheme = blackTheme,
|
||||
aodEnabled = aodEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
val application = (this[APPLICATION_KEY] as TomatoApplication)
|
||||
val appPreferenceRepository = application.container.appPreferenceRepository
|
||||
val appTimerRepository = application.container.appTimerRepository
|
||||
val appBillingManager = application.container.billingManager
|
||||
|
||||
SettingsViewModel(
|
||||
billingManager = appBillingManager,
|
||||
preferenceRepository = appPreferenceRepository,
|
||||
timerRepository = appTimerRepository,
|
||||
)
|
||||
|
||||
@@ -28,13 +28,77 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
val primaryLight = Color(0xFF4C662B)
|
||||
val onPrimaryLight = Color(0xFFFFFFFF)
|
||||
val primaryContainerLight = Color(0xFFCDEDA3)
|
||||
val onPrimaryContainerLight = Color(0xFF354E16)
|
||||
val secondaryLight = Color(0xFF586249)
|
||||
val onSecondaryLight = Color(0xFFFFFFFF)
|
||||
val secondaryContainerLight = Color(0xFFDCE7C8)
|
||||
val onSecondaryContainerLight = Color(0xFF404A33)
|
||||
val tertiaryLight = Color(0xFF386663)
|
||||
val onTertiaryLight = Color(0xFFFFFFFF)
|
||||
val tertiaryContainerLight = Color(0xFFBCECE7)
|
||||
val onTertiaryContainerLight = Color(0xFF1F4E4B)
|
||||
val errorLight = Color(0xFFBA1A1A)
|
||||
val onErrorLight = Color(0xFFFFFFFF)
|
||||
val errorContainerLight = Color(0xFFFFDAD6)
|
||||
val onErrorContainerLight = Color(0xFF93000A)
|
||||
val backgroundLight = Color(0xFFF9FAEF)
|
||||
val onBackgroundLight = Color(0xFF1A1C16)
|
||||
val surfaceLight = Color(0xFFF9FAEF)
|
||||
val onSurfaceLight = Color(0xFF1A1C16)
|
||||
val surfaceVariantLight = Color(0xFFE1E4D5)
|
||||
val onSurfaceVariantLight = Color(0xFF44483D)
|
||||
val outlineLight = Color(0xFF75796C)
|
||||
val outlineVariantLight = Color(0xFFC5C8BA)
|
||||
val scrimLight = Color(0xFF000000)
|
||||
val inverseSurfaceLight = Color(0xFF2F312A)
|
||||
val inverseOnSurfaceLight = Color(0xFFF1F2E6)
|
||||
val inversePrimaryLight = Color(0xFFB1D18A)
|
||||
val surfaceDimLight = Color(0xFFDADBD0)
|
||||
val surfaceBrightLight = Color(0xFFF9FAEF)
|
||||
val surfaceContainerLowestLight = Color(0xFFFFFFFF)
|
||||
val surfaceContainerLowLight = Color(0xFFF3F4E9)
|
||||
val surfaceContainerLight = Color(0xFFEEEFE3)
|
||||
val surfaceContainerHighLight = Color(0xFFE8E9DE)
|
||||
val surfaceContainerHighestLight = Color(0xFFE2E3D8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
val primaryDark = Color(0xFFB1D18A)
|
||||
val onPrimaryDark = Color(0xFF1F3701)
|
||||
val primaryContainerDark = Color(0xFF354E16)
|
||||
val onPrimaryContainerDark = Color(0xFFCDEDA3)
|
||||
val secondaryDark = Color(0xFFBFCBAD)
|
||||
val onSecondaryDark = Color(0xFF2A331E)
|
||||
val secondaryContainerDark = Color(0xFF404A33)
|
||||
val onSecondaryContainerDark = Color(0xFFDCE7C8)
|
||||
val tertiaryDark = Color(0xFFA0D0CB)
|
||||
val onTertiaryDark = Color(0xFF003735)
|
||||
val tertiaryContainerDark = Color(0xFF1F4E4B)
|
||||
val onTertiaryContainerDark = Color(0xFFBCECE7)
|
||||
val errorDark = Color(0xFFFFB4AB)
|
||||
val onErrorDark = Color(0xFF690005)
|
||||
val errorContainerDark = Color(0xFF93000A)
|
||||
val onErrorContainerDark = Color(0xFFFFDAD6)
|
||||
val backgroundDark = Color(0xFF12140E)
|
||||
val onBackgroundDark = Color(0xFFE2E3D8)
|
||||
val surfaceDark = Color(0xFF12140E)
|
||||
val onSurfaceDark = Color(0xFFE2E3D8)
|
||||
val surfaceVariantDark = Color(0xFF44483D)
|
||||
val onSurfaceVariantDark = Color(0xFFC5C8BA)
|
||||
val outlineDark = Color(0xFF8F9285)
|
||||
val outlineVariantDark = Color(0xFF44483D)
|
||||
val scrimDark = Color(0xFF000000)
|
||||
val inverseSurfaceDark = Color(0xFFE2E3D8)
|
||||
val inverseOnSurfaceDark = Color(0xFF2F312A)
|
||||
val inversePrimaryDark = Color(0xFF4C662B)
|
||||
val surfaceDimDark = Color(0xFF12140E)
|
||||
val surfaceBrightDark = Color(0xFF383A32)
|
||||
val surfaceContainerLowestDark = Color(0xFF0C0F09)
|
||||
val surfaceContainerLowDark = Color(0xFF1A1C16)
|
||||
val surfaceContainerDark = Color(0xFF1E201A)
|
||||
val surfaceContainerHighDark = Color(0xFF282B24)
|
||||
val surfaceContainerHighestDark = Color(0xFF33362E)
|
||||
|
||||
object CustomColors {
|
||||
var black = false
|
||||
|
||||
@@ -1,3 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
@@ -19,26 +36,80 @@ import androidx.core.view.WindowCompat
|
||||
import com.materialkolor.dynamiccolor.ColorSpec
|
||||
import com.materialkolor.rememberDynamicColorScheme
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
private val lightScheme = lightColorScheme(
|
||||
primary = primaryLight,
|
||||
onPrimary = onPrimaryLight,
|
||||
primaryContainer = primaryContainerLight,
|
||||
onPrimaryContainer = onPrimaryContainerLight,
|
||||
secondary = secondaryLight,
|
||||
onSecondary = onSecondaryLight,
|
||||
secondaryContainer = secondaryContainerLight,
|
||||
onSecondaryContainer = onSecondaryContainerLight,
|
||||
tertiary = tertiaryLight,
|
||||
onTertiary = onTertiaryLight,
|
||||
tertiaryContainer = tertiaryContainerLight,
|
||||
onTertiaryContainer = onTertiaryContainerLight,
|
||||
error = errorLight,
|
||||
onError = onErrorLight,
|
||||
errorContainer = errorContainerLight,
|
||||
onErrorContainer = onErrorContainerLight,
|
||||
background = backgroundLight,
|
||||
onBackground = onBackgroundLight,
|
||||
surface = surfaceLight,
|
||||
onSurface = onSurfaceLight,
|
||||
surfaceVariant = surfaceVariantLight,
|
||||
onSurfaceVariant = onSurfaceVariantLight,
|
||||
outline = outlineLight,
|
||||
outlineVariant = outlineVariantLight,
|
||||
scrim = scrimLight,
|
||||
inverseSurface = inverseSurfaceLight,
|
||||
inverseOnSurface = inverseOnSurfaceLight,
|
||||
inversePrimary = inversePrimaryLight,
|
||||
surfaceDim = surfaceDimLight,
|
||||
surfaceBright = surfaceBrightLight,
|
||||
surfaceContainerLowest = surfaceContainerLowestLight,
|
||||
surfaceContainerLow = surfaceContainerLowLight,
|
||||
surfaceContainer = surfaceContainerLight,
|
||||
surfaceContainerHigh = surfaceContainerHighLight,
|
||||
surfaceContainerHighest = surfaceContainerHighestLight,
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color(0xFFFFFBFE),
|
||||
surface = Color(0xFFFFFBFE),
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.White,
|
||||
onTertiary = Color.White,
|
||||
onBackground = Color(0xFF1C1B1F),
|
||||
onSurface = Color(0xFF1C1B1F),
|
||||
*/
|
||||
private val darkScheme = darkColorScheme(
|
||||
primary = primaryDark,
|
||||
onPrimary = onPrimaryDark,
|
||||
primaryContainer = primaryContainerDark,
|
||||
onPrimaryContainer = onPrimaryContainerDark,
|
||||
secondary = secondaryDark,
|
||||
onSecondary = onSecondaryDark,
|
||||
secondaryContainer = secondaryContainerDark,
|
||||
onSecondaryContainer = onSecondaryContainerDark,
|
||||
tertiary = tertiaryDark,
|
||||
onTertiary = onTertiaryDark,
|
||||
tertiaryContainer = tertiaryContainerDark,
|
||||
onTertiaryContainer = onTertiaryContainerDark,
|
||||
error = errorDark,
|
||||
onError = onErrorDark,
|
||||
errorContainer = errorContainerDark,
|
||||
onErrorContainer = onErrorContainerDark,
|
||||
background = backgroundDark,
|
||||
onBackground = onBackgroundDark,
|
||||
surface = surfaceDark,
|
||||
onSurface = onSurfaceDark,
|
||||
surfaceVariant = surfaceVariantDark,
|
||||
onSurfaceVariant = onSurfaceVariantDark,
|
||||
outline = outlineDark,
|
||||
outlineVariant = outlineVariantDark,
|
||||
scrim = scrimDark,
|
||||
inverseSurface = inverseSurfaceDark,
|
||||
inverseOnSurface = inverseOnSurfaceDark,
|
||||
inversePrimary = inversePrimaryDark,
|
||||
surfaceDim = surfaceDimDark,
|
||||
surfaceBright = surfaceBrightDark,
|
||||
surfaceContainerLowest = surfaceContainerLowestDark,
|
||||
surfaceContainerLow = surfaceContainerLowDark,
|
||||
surfaceContainer = surfaceContainerDark,
|
||||
surfaceContainerHigh = surfaceContainerHighDark,
|
||||
surfaceContainerHighest = surfaceContainerHighestDark,
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@@ -56,8 +127,8 @@ fun TomatoTheme(
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
darkTheme -> darkScheme
|
||||
else -> lightScheme
|
||||
}
|
||||
|
||||
val view = LocalView.current
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.timerScreen
|
||||
@@ -92,10 +102,12 @@ import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerAction
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerMode
|
||||
import org.nsh07.pomodoro.ui.timerScreen.viewModel.TimerState
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun SharedTransitionScope.TimerScreen(
|
||||
timerState: TimerState,
|
||||
isPlus: Boolean,
|
||||
progress: () -> Float,
|
||||
onAction: (TimerAction) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
@@ -148,7 +160,8 @@ fun SharedTransitionScope.TimerScreen(
|
||||
when (it) {
|
||||
TimerMode.BRAND ->
|
||||
Text(
|
||||
stringResource(R.string.app_name),
|
||||
if (!isPlus) stringResource(R.string.app_name)
|
||||
else stringResource(R.string.app_name_plus),
|
||||
style = TextStyle(
|
||||
fontFamily = robotoFlexTopBar,
|
||||
fontSize = 32.sp,
|
||||
@@ -541,6 +554,7 @@ fun TimerScreenPreview() {
|
||||
SharedTransitionLayout {
|
||||
TimerScreen(
|
||||
timerState,
|
||||
isPlus = true,
|
||||
{ 0.3f },
|
||||
{}
|
||||
)
|
||||
|
||||
@@ -1,8 +1,18 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.ui.timerScreen.viewModel
|
||||
@@ -85,6 +95,9 @@ class TimerViewModel(
|
||||
timerRepository.vibrateEnabled =
|
||||
preferenceRepository.getBooleanPreference("vibrate_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("vibrate_enabled", true)
|
||||
timerRepository.dndEnabled =
|
||||
preferenceRepository.getBooleanPreference("dnd_enabled")
|
||||
?: preferenceRepository.saveBooleanPreference("dnd_enabled", false)
|
||||
|
||||
timerRepository.alarmSoundUri = (
|
||||
preferenceRepository.getStringPreference("alarm_sound")
|
||||
|
||||
30
app/src/main/res/drawable/bmc.xml
Normal file
30
app/src/main/res/drawable/bmc.xml
Normal file
@@ -0,0 +1,30 @@
|
||||
<!--
|
||||
~ Copyright (c) 2025 Nishant Mishra
|
||||
~
|
||||
~ This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
~
|
||||
~ Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
~ General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~
|
||||
~ Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
~ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
~ Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with Tomato.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="40dp"
|
||||
android:height="40dp"
|
||||
android:viewportWidth="1279"
|
||||
android:viewportHeight="1279">
|
||||
<path
|
||||
android:fillAlpha="0.7"
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m670.4,590.8c-45.9,19.7 -98.1,42 -165.6,42 -28.3,-0.1 -56.4,-3.9 -83.6,-11.5l46.7,479.8c1.7,20 10.8,38.8 25.6,52.4 14.8,13.6 34.2,21.2 54.3,21.2 0,0 66.3,3.4 88.4,3.4 23.8,0 95.2,-3.4 95.2,-3.4 20.1,0 39.5,-7.6 54.3,-21.2 14.8,-13.6 23.9,-32.3 25.6,-52.4l50.1,-530.2c-22.4,-7.6 -44.9,-12.7 -70.4,-12.7 -44,-0 -79.5,15.1 -120.4,32.7z" />
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="m1077.3,341.8 l-7,-35.5c-6.3,-31.8 -20.6,-61.9 -53.3,-73.5 -10.5,-3.7 -22.4,-5.3 -30.4,-12.9 -8,-7.6 -10.4,-19.5 -12.3,-30.4 -3.4,-20.1 -6.7,-40.3 -10.2,-60.4 -3,-17.3 -5.5,-36.7 -13.4,-52.6 -10.3,-21.3 -31.7,-33.8 -53,-42 -10.9,-4.1 -22.1,-7.5 -33.4,-10.3 -53.2,-14 -109.2,-19.2 -163.9,-22.1 -65.7,-3.6 -131.6,-2.5 -197.2,3.3 -48.8,4.4 -100.2,9.8 -146.6,26.7 -16.9,6.2 -34.4,13.6 -47.3,26.7 -15.8,16.1 -21,41 -9.4,61 8.2,14.2 22.1,24.3 36.9,31 19.2,8.6 39.3,15.1 59.8,19.5 57.3,12.7 116.6,17.6 175.2,19.8 64.9,2.6 129.9,0.5 194.4,-6.3 16,-1.8 31.9,-3.9 47.8,-6.3 18.7,-2.9 30.8,-27.4 25.2,-44.4 -6.6,-20.4 -24.4,-28.3 -44.4,-25.2 -3,0.5 -5.9,0.9 -8.9,1.3l-2.1,0.3c-6.8,0.9 -13.6,1.7 -20.4,2.4 -14.1,1.5 -28.1,2.8 -42.3,3.7 -31.6,2.2 -63.3,3.2 -95,3.3 -31.1,0 -62.3,-0.9 -93.4,-2.9 -14.2,-0.9 -28.3,-2.1 -42.4,-3.5 -6.4,-0.7 -12.8,-1.4 -19.2,-2.2l-6.1,-0.8 -1.3,-0.2 -6.3,-0.9c-12.9,-1.9 -25.8,-4.2 -38.6,-6.9 -1.3,-0.3 -2.4,-1 -3.3,-2 -0.8,-1 -1.3,-2.3 -1.3,-3.6 0,-1.3 0.4,-2.6 1.3,-3.6 0.8,-1 2,-1.7 3.3,-2h0.2c11.1,-2.4 22.2,-4.4 33.4,-6.1 3.7,-0.6 7.5,-1.2 11.2,-1.7h0.1c7,-0.5 14,-1.7 21,-2.5 60.6,-6.3 121.6,-8.5 182.5,-6.4 29.6,0.9 59.1,2.6 88.6,5.6 6.3,0.7 12.6,1.3 18.9,2.1 2.4,0.3 4.8,0.6 7.3,0.9l4.9,0.7c14.2,2.1 28.4,4.7 42.5,7.7 20.9,4.5 47.7,6 57,28.9 3,7.3 4.3,15.3 5.9,23l2.1,9.7c0.1,0.2 0.1,0.4 0.1,0.5 4.9,22.9 9.8,45.9 14.8,68.8 0.4,1.7 0.4,3.4 0,5.1 -0.3,1.7 -1,3.3 -2,4.7 -1,1.4 -2.3,2.6 -3.7,3.5 -1.5,0.9 -3.1,1.5 -4.8,1.7h-0.1l-3,0.4 -3,0.4c-9.4,1.2 -18.9,2.4 -28.3,3.4 -18.6,2.1 -37.3,4 -55.9,5.5 -37.1,3.1 -74.3,5.1 -111.6,6.1 -19,0.5 -38,0.7 -56.9,0.7 -75.5,-0.1 -151,-4.4 -226,-13.1 -8.1,-1 -16.2,-2 -24.4,-3 6.3,0.8 -4.6,-0.6 -6.8,-0.9 -5.2,-0.7 -10.3,-1.5 -15.5,-2.3 -17.3,-2.6 -34.5,-5.8 -51.8,-8.6 -20.9,-3.4 -40.9,-1.7 -59.8,8.6 -15.5,8.5 -28.1,21.5 -36,37.3 -8.2,16.9 -10.6,35.2 -14.2,53.3 -3.6,18.1 -9.3,37.6 -7.2,56.2 4.6,40.1 32.7,72.8 73.1,80.1 38,6.9 76.2,12.5 114.4,17.2 150.4,18.4 302.3,20.6 453.2,6.6 12.3,-1.1 24.6,-2.4 36.8,-3.8 3.8,-0.4 7.7,0 11.3,1.3 3.6,1.3 6.9,3.3 9.7,6 2.7,2.7 4.8,6 6.1,9.6 1.3,3.6 1.8,7.5 1.4,11.3l-3.8,37.1c-7.7,75 -15.4,150.1 -23.1,225.1 -8,78.8 -16.1,157.6 -24.2,236.3 -2.3,22.2 -4.6,44.4 -6.9,66.5 -2.2,21.8 -2.5,44.4 -6.7,65.9 -6.5,33.9 -29.5,54.8 -63,62.4 -30.7,7 -62.1,10.7 -93.6,10.9 -34.9,0.2 -69.8,-1.4 -104.7,-1.2 -37.3,0.2 -82.9,-3.2 -111.7,-31 -25.3,-24.4 -28.8,-62.5 -32.2,-95.5 -4.6,-43.7 -9.1,-87.3 -13.6,-131l-25.3,-242.8 -16.4,-157.1c-0.3,-2.6 -0.6,-5.2 -0.8,-7.8 -2,-18.7 -15.2,-37.1 -36.1,-36.1 -17.9,0.8 -38.2,16 -36.1,36.1l12.1,116.5 25.1,240.9c7.1,68.4 14.3,136.9 21.4,205.3 1.4,13.1 2.7,26.3 4.1,39.4 7.9,71.7 62.6,110.3 130.3,121.1 39.6,6.4 80.1,7.7 120.3,8.3 51.5,0.8 103.5,2.8 154.1,-6.5 75,-13.8 131.3,-63.9 139.4,-141.6 2.3,-22.4 4.6,-44.9 6.9,-67.3 7.6,-74.2 15.2,-148.5 22.9,-222.7l24.9,-242.6 11.4,-111.2c0.6,-5.5 2.9,-10.7 6.6,-14.8 3.7,-4.1 8.7,-6.9 14.1,-7.9 21.5,-4.2 42,-11.3 57.2,-27.7 24.3,-26 29.1,-59.9 20.5,-94.1zM998.5,383c-7.7,7.3 -19.3,10.7 -30.8,12.4 -128.7,19.1 -259.3,28.8 -389.4,24.5 -93.1,-3.2 -185.3,-13.5 -277.5,-26.5 -9,-1.3 -18.8,-2.9 -25,-9.6 -11.7,-12.6 -6,-37.9 -2.9,-53 2.8,-13.9 8.1,-32.4 24.7,-34.4 25.8,-3 55.8,7.9 81.3,11.7 30.7,4.7 61.6,8.4 92.6,11.3 132.2,12 266.6,10.2 398.2,-7.4 24,-3.2 47.9,-7 71.7,-11.2 21.2,-3.8 44.7,-10.9 57.6,11 8.8,15 10,35 8.6,51.9 -0.4,7.4 -3.6,14.3 -9,19.4z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#e3e3e3"
|
||||
android:pathData="M440,720q-117,0 -198.5,-81.5T160,440v-240q0,-33 23.5,-56.5T240,120h500q58,0 99,41t41,99q0,58 -41,99t-99,41h-20v40q0,117 -81.5,198.5T440,720ZM240,320h400v-120L240,200v120ZM720,320h20q25,0 42.5,-17.5T800,260q0,-25 -17.5,-42.5T740,200h-20v120ZM200,840q-17,0 -28.5,-11.5T160,800q0,-17 11.5,-28.5T200,760h560q17,0 28.5,11.5T800,800q0,17 -11.5,28.5T760,840L200,840Z" />
|
||||
</vector>
|
||||
26
app/src/main/res/drawable/dnd.xml
Normal file
26
app/src/main/res/drawable/dnd.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<!--
|
||||
~ Copyright (c) 2025 Nishant Mishra
|
||||
~
|
||||
~ This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
~
|
||||
~ Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
~ General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~
|
||||
~ Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
~ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
~ Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with Tomato.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#e3e3e3"
|
||||
android:pathData="M320,520h320q17,0 28.5,-11.5T680,480q0,-17 -11.5,-28.5T640,440L320,440q-17,0 -28.5,11.5T280,480q0,17 11.5,28.5T320,520ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880Z" />
|
||||
</vector>
|
||||
@@ -1,10 +1,27 @@
|
||||
<!--
|
||||
~ Copyright (c) 2025 Nishant Mishra
|
||||
~
|
||||
~ This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
~
|
||||
~ Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
~ General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~
|
||||
~ Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
~ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
~ Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with Tomato.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="6.35"
|
||||
android:viewportHeight="6.35">
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M0.6744,6.3347C0.5991,6.3185 0.471,6.275 0.4362,6.2538 0.4204,6.2442 0.7343,5.9434 1.9712,4.7827L3.5262,3.3236 4.159,3.8713C4.5071,4.1725 4.7868,4.4231 4.7806,4.4283 4.767,4.4398 1.3189,6.2241 1.2202,6.2708 1.0678,6.3428 0.8376,6.3698 0.6744,6.3347ZM0.0712,5.8885C-0.0032,5.736 0,5.8606 0,3.1751c0,-2.6939 -0.0037,-2.5574 0.0735,-2.7187 0.0195,-0.0409 0.0396,-0.0743 0.0445,-0.0743 0.0049,0 0.7041,0.6015 1.5538,1.3366L3.2165,3.0555 1.6709,4.5056C0.8208,5.3031 0.1205,5.9557 0.1146,5.9557c-0.0059,0 -0.0254,-0.0302 -0.0435,-0.0672zM4.5028,3.6306 L3.826,3.0448 4.377,2.5266C4.6801,2.2415 4.9336,2.0083 4.9405,2.0083c0.0168,0 0.9615,0.4887 1.0486,0.5425C6.2037,2.6831 6.35,2.9363 6.35,3.1751 6.35,3.4137 6.2088,3.6595 5.9939,3.7948 5.9253,3.838 5.1959,4.2187 5.1846,4.2171 5.1818,4.2168 4.875,3.9528 4.5028,3.6306ZM3.241,2.5383C3.099,2.4141 2.409,1.8158 1.7076,1.2088 0.5332,0.1924 0.4344,0.1038 0.4572,0.0882 0.5059,0.0547 0.6943,0.008 0.8067,0.0015 0.9426,-0.0064 1.0774,0.0175 1.2002,0.0715 1.247,0.092 2.0206,0.4896 2.9193,0.955L4.5534,1.8012 4.0396,2.2834C3.757,2.5485 3.5198,2.7652 3.5125,2.7648 3.5052,2.7644 3.383,2.6625 3.241,2.5383Z"
|
||||
android:strokeWidth="0.0120146" />
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="m12.213,13.862c-0.337,-0.338 -0.884,-0.338 -1.22,0 -0.883,0.884 -6.416,6.433 -7.161,7.175 0.865,0.922 2.431,1.145 3.528,0.475l7.946,-4.554c-0.733,-0.731 -2.349,-2.35 -3.092,-3.096zM10.993,10.2c0.337,0.338 0.884,0.338 1.22,0l1.856,-1.86 -0.002,-0.001L15.33,7.077 7.39,2.505C6.271,1.804 4.658,2.044 3.79,2.984 4.518,3.707 10.125,9.332 10.993,10.2ZM9.775,12.643c0.337,-0.338 0.337,-0.886 0,-1.224L4.805,6.434 3.02,4.652C3.007,4.756 2.998,4.87 3,4.98l0.001,14.069c0.004,0.132 0.023,0.286 0.044,0.418 1.175,-1.151 5.501,-5.573 6.73,-6.825zM19.519,9.489 L16.878,7.968c-0.774,0.768 -2.656,2.66 -3.447,3.452 -0.338,0.338 -0.338,0.886 0,1.224 0.796,0.792 2.645,2.656 3.424,3.428l2.639,-1.512c1.996,-0.99 2.011,-4.063 0.025,-5.071z"
|
||||
android:strokeWidth="0.599991" />
|
||||
</vector>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<string name="dynamic">ديناميكي</string>
|
||||
<string name="color">لون</string>
|
||||
<string name="system_default">النظام (الافتراضي)</string>
|
||||
<string name="alarm">منبه</string>
|
||||
<string name="alarm">المنبه</string>
|
||||
<string name="light">فاتح</string>
|
||||
<string name="dark">داكن</string>
|
||||
<string name="choose_theme">اختر سمة</string>
|
||||
@@ -31,7 +31,7 @@
|
||||
<string name="alarm_desc">التنبيه عند انتهاء المؤقت</string>
|
||||
<string name="vibrate">اهتزاز</string>
|
||||
<string name="vibrate_desc">الاهتزاز عندما ينتهي المؤقت</string>
|
||||
<string name="theme">سمة</string>
|
||||
<string name="theme">السمة</string>
|
||||
<string name="settings">الاعدادات</string>
|
||||
<string name="session_length">طول الجلسة</string>
|
||||
<string name="session_length_desc">فترات التركيز في الجلسة الواحدة: %1$d</string>
|
||||
@@ -57,4 +57,9 @@
|
||||
<string name="timer">المؤقت</string>
|
||||
<string name="timer_progress">تقدم المؤقت</string>
|
||||
<string name="last_year">السنة الماضية</string>
|
||||
<string name="always_on_display">العرض الدائم علي الشاشة</string>
|
||||
<string name="always_on_display_desc">اضغط في أي مكان أثناء عرض المؤقت للتبديل إلى وضع العرض الدائم على الشاشة</string>
|
||||
<string name="appearance">المظهر</string>
|
||||
<string name="durations">الفترات</string>
|
||||
<string name="sound">الصوت</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<string name="color_scheme">Farbschema</string>
|
||||
<string name="dynamic">Dynamisch</string>
|
||||
<string name="color">Farbe</string>
|
||||
<string name="system_default">Systemstandard</string>
|
||||
<string name="system_default">System</string>
|
||||
<string name="alarm">Alarm</string>
|
||||
<string name="light">Hell</string>
|
||||
<string name="dark">Dunkel</string>
|
||||
@@ -56,4 +56,12 @@
|
||||
<string name="up_next">Als nächstes</string>
|
||||
<string name="timer">Timer</string>
|
||||
<string name="timer_progress">Timer-Fortschritt</string>
|
||||
<string name="always_on_display">Always-On Display</string>
|
||||
<string name="always_on_display_desc">Tippe irgendwo, während der Timer angezeigt wird, um in den AOD-Modus zu wechseln.</string>
|
||||
<string name="last_year">Letztes Jahr</string>
|
||||
<string name="appearance">Aussehen</string>
|
||||
<string name="durations">Dauer</string>
|
||||
<string name="sound">Sound</string>
|
||||
<string name="dnd">Bitte nicht stören</string>
|
||||
<string name="dnd_desc">‚Bitte nicht stören‘ aktivieren, wenn ein Fokus-Timer läuft</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="start">Démarrer</string>
|
||||
<string name="stop">Arrêter</string>
|
||||
<string name="stop">Pause</string>
|
||||
<string name="focus">Concentration</string>
|
||||
<string name="short_break">Pause courte</string>
|
||||
<string name="long_break">Pause longue</string>
|
||||
@@ -18,7 +18,7 @@
|
||||
<string name="color_scheme">Thème de couleurs</string>
|
||||
<string name="dynamic">Dynamique</string>
|
||||
<string name="color">Couleur</string>
|
||||
<string name="system_default">Valeur par défaut du système</string>
|
||||
<string name="system_default">Système</string>
|
||||
<string name="alarm">Alarme</string>
|
||||
<string name="light">Clair</string>
|
||||
<string name="dark">Sombre</string>
|
||||
@@ -29,7 +29,7 @@
|
||||
<string name="black_theme">Thème noir</string>
|
||||
<string name="black_theme_desc">Utiliser un thème sombre noir pur</string>
|
||||
<string name="alarm_desc">Faire sonner l’alarme à la fin du minuteur</string>
|
||||
<string name="vibrate">Vibrer</string>
|
||||
<string name="vibrate">Vibration</string>
|
||||
<string name="vibrate_desc">Faire vibrer à la fin du minuteur</string>
|
||||
<string name="theme">Thème</string>
|
||||
<string name="settings">Paramètres</string>
|
||||
@@ -38,7 +38,7 @@
|
||||
<string name="pomodoro_info">Une \"session\" est une séquence d’intervalles Pomodoro comprenant des phases de concentration, des pauses courtes et une pause longue. La dernière pause d’une session est toujours une pause longue.</string>
|
||||
<string name="stats">Statistiques</string>
|
||||
<string name="today">Aujourd\'hui</string>
|
||||
<string name="break_">Pause</string>
|
||||
<string name="break_">Pauses</string>
|
||||
<string name="last_week">7 derniers jours</string>
|
||||
<string name="focus_per_day_avg">concentration moyenne par jour</string>
|
||||
<string name="more_info">Plus d\'infos</string>
|
||||
@@ -59,4 +59,15 @@
|
||||
<string name="last_year">12 derniers mois</string>
|
||||
<string name="always_on_display_desc">Appuyez n\'importe où lors de l\'affichage du minuteur pour passer en mode AOD</string>
|
||||
<string name="always_on_display">Affichage Permanent</string>
|
||||
<string name="appearance">Apparence</string>
|
||||
<string name="durations">Durées</string>
|
||||
<string name="sound">Son</string>
|
||||
<string name="dnd">Ne pas déranger</string>
|
||||
<string name="dnd_desc">Activer le mode ne pas déranger pendant un minuteur de concentration</string>
|
||||
<string name="dynamic_color">Couleur dynamique</string>
|
||||
<string name="dynamic_color_desc">Adapter les couleurs à celles de votre fond d\'écran</string>
|
||||
<string name="tomato_foss_desc">Toutes les fonctionnalités sont déverrouillées dans cette version. Si mon application a fait une différence dans votre vie, veuillez envisager de me soutenir en faisant un don de %1$s.</string>
|
||||
<string name="app_name_plus">Tomato+</string>
|
||||
<string name="get_plus">Obtenir Tomato+</string>
|
||||
<string name="tomato_foss">Tomato FOSS</string>
|
||||
</resources>
|
||||
|
||||
@@ -3,4 +3,63 @@
|
||||
<string name="start">प्रारंभ करें</string>
|
||||
<string name="stop">विराम करें</string>
|
||||
<string name="focus">ध्यान</string>
|
||||
<string name="alarm">अलार्म</string>
|
||||
<string name="alarm_desc">टाइमर पूरा होने पर अलार्म बजाएँ</string>
|
||||
<string name="alarm_sound">अलार्म ध्वनि</string>
|
||||
<string name="always_on_display">ऑलवेज ऑन डिस्प्ले</string>
|
||||
<string name="always_on_display_desc">AOD मोड चालू करने के लिए टाइमर देखते समय कहीं भी टैप करें</string>
|
||||
<string name="black_theme">काली थीम</string>
|
||||
<string name="black_theme_desc">पूर्णतः काले रंग की थीम का उपयोग करें</string>
|
||||
<string name="break_">विश्राम</string>
|
||||
<string name="choose_color_scheme">रंग स्कीम चुनें</string>
|
||||
<string name="choose_theme">थीम चुनें</string>
|
||||
<string name="color">रंग</string>
|
||||
<string name="color_scheme">रंग स्कीम</string>
|
||||
<string name="completed">पूर्ण</string>
|
||||
<string name="dark">काला</string>
|
||||
<string name="dynamic">डायनामिक</string>
|
||||
<string name="exit">बंद करें</string>
|
||||
<string name="focus_per_day_avg">प्रतिदिन ध्यान (औसत)</string>
|
||||
<string name="last_month">पिछले महीने</string>
|
||||
<string name="last_week">पिछले सप्ताह</string>
|
||||
<string name="sound">ध्वनि</string>
|
||||
<string name="last_year">पिछले साल</string>
|
||||
<string name="light">सफ़ेद</string>
|
||||
<string name="long_break">दीर्घ विश्राम</string>
|
||||
<string name="min_remaining_notification">%1$s मिनट शेष</string>
|
||||
<string name="monthly_productivity_analysis">मासिक उत्पादकता विश्लेषण</string>
|
||||
<string name="more">अधिक</string>
|
||||
<string name="more_info">अधिक जानकारी</string>
|
||||
<string name="ok">ठीक</string>
|
||||
<string name="pause">रोकें</string>
|
||||
<string name="paused">रुका हुआ</string>
|
||||
<string name="play">शुरू करें</string>
|
||||
<string name="pomodoro_info">एक \"सत्र\" पोमोडोरो अंतरालों का एक क्रम होता है जिसमें ध्यान अंतराल, लघु विश्राम अंतराल और एक दीर्घ विश्राम अंतराल शामिल होता है। सत्र का अंतिम विश्राम हमेशा एक दीर्घ विश्राम होता है।</string>
|
||||
<string name="productivity_analysis">उत्पादकता विश्लेषण</string>
|
||||
<string name="productivity_analysis_desc">दिन के अलग-अलग समय पर ध्यान अवधि</string>
|
||||
<string name="restart">पुनः शुरू करें</string>
|
||||
<string name="session_length">सत्र की अवधि</string>
|
||||
<string name="session_length_desc">एक सत्र में ध्यान अंतराल: %1$d</string>
|
||||
<string name="settings">सेटिंग</string>
|
||||
<string name="short_break">लघु विश्राम</string>
|
||||
<string name="skip">अगला अंतराल</string>
|
||||
<string name="skip_to_next">अगले पर जाएं</string>
|
||||
<string name="start_next">अगला शुरू करें</string>
|
||||
<string name="stats">आँकड़े</string>
|
||||
<string name="stop_alarm">अलार्म बंद करें</string>
|
||||
<string name="stop_alarm_dialog_text">वर्तमान टाइमर सत्र पूरा हो गया है। अलार्म बंद करने के लिए कहीं भी टैप करें।</string>
|
||||
<string name="stop_alarm_question">अलार्म बंद करें?</string>
|
||||
<string name="system_default">सिस्टम</string>
|
||||
<string name="theme">थीम</string>
|
||||
<string name="timer">टाइमर</string>
|
||||
<string name="timer_progress">टाइमर पूर्णता</string>
|
||||
<string name="timer_session_count">%2$d में से %1$d</string>
|
||||
<string name="today">आज</string>
|
||||
<string name="up_next">आगामी</string>
|
||||
<string name="up_next_notification">आगामी: %1$s (%2$s)</string>
|
||||
<string name="vibrate">वाइब्रेट</string>
|
||||
<string name="vibrate_desc">टाइमर पूरा होने पर वाइब्रेट करें</string>
|
||||
<string name="weekly_productivity_analysis">साप्ताहिक उत्पादकता विश्लेषण</string>
|
||||
<string name="appearance">दिखावट</string>
|
||||
<string name="durations">अवधियां</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<string name="color_scheme">Renk şeması</string>
|
||||
<string name="dynamic">Dinamik</string>
|
||||
<string name="color">Renk</string>
|
||||
<string name="system_default">Sistem varsayılanı</string>
|
||||
<string name="system_default">Sistem</string>
|
||||
<string name="light">Açık</string>
|
||||
<string name="dark">Koyu</string>
|
||||
<string name="choose_theme">Tema seçin</string>
|
||||
@@ -61,4 +61,7 @@
|
||||
<string name="always_on_display_desc">Zamanlayıcıyı görüntülerken AOD moduna geçmek için herhangi bir yere dokunun</string>
|
||||
<string name="appearance">Görünüm</string>
|
||||
<string name="durations">Süreler</string>
|
||||
<string name="sound">Ses</string>
|
||||
<string name="dnd">Rahatsız Etmeyin</string>
|
||||
<string name="dnd_desc">Odaklanma sayacı çalışırken RE\'yi aç</string>
|
||||
</resources>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<string name="ok">ОК</string>
|
||||
<string name="dynamic">Динамічна</string>
|
||||
<string name="color">Колір</string>
|
||||
<string name="system_default">Налаштування системи</string>
|
||||
<string name="system_default">Система</string>
|
||||
<string name="alarm">Сигнал</string>
|
||||
<string name="light">Світла</string>
|
||||
<string name="dark">Темна</string>
|
||||
@@ -57,4 +57,17 @@
|
||||
<string name="pause">Пауза</string>
|
||||
<string name="play">Грати</string>
|
||||
<string name="last_year">Минулого року</string>
|
||||
<string name="always_on_display">Always On Display</string>
|
||||
<string name="always_on_display_desc">Натисніть будь-де під час перегляду таймера, щоб перейти в режим AOD</string>
|
||||
<string name="appearance">Вигляд</string>
|
||||
<string name="durations">Тривалість</string>
|
||||
<string name="sound">Звук</string>
|
||||
<string name="dnd">Не турбувати</string>
|
||||
<string name="dnd_desc">Активувати режим «Не турбувати», доки таймер активний</string>
|
||||
<string name="app_name_plus">Tomato+</string>
|
||||
<string name="get_plus">Отримати Tomato+</string>
|
||||
<string name="dynamic_color">Динамічний колір</string>
|
||||
<string name="dynamic_color_desc">Адаптуйте кольори теми зі своїх шпалер</string>
|
||||
<string name="tomato_foss">Tomato FOSS</string>
|
||||
<string name="tomato_foss_desc">Усі функції розблоковано в цій версії. Якщо мій додаток допоміг Вам у житті, будь ласка, підтримайте мене, зробивши пожертву на %1$s.</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<string name="color_scheme">配色方案</string>
|
||||
<string name="dynamic">动态</string>
|
||||
<string name="color">颜色</string>
|
||||
<string name="system_default">系统默认</string>
|
||||
<string name="system_default">系统</string>
|
||||
<string name="alarm">响铃</string>
|
||||
<string name="light">亮色</string>
|
||||
<string name="dark">暗色</string>
|
||||
@@ -59,4 +59,9 @@
|
||||
<string name="last_year">去年</string>
|
||||
<string name="always_on_display">息屏显示</string>
|
||||
<string name="always_on_display_desc">查看计时器时点击任意位置切换至 AOD 模式</string>
|
||||
<string name="appearance">外观</string>
|
||||
<string name="durations">时长</string>
|
||||
<string name="sound">声音</string>
|
||||
<string name="dnd">勿扰模式</string>
|
||||
<string name="dnd_desc">运行「专注」计时器时打开勿扰</string>
|
||||
</resources>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<string name="color_scheme">配色方案</string>
|
||||
<string name="dynamic">動態</string>
|
||||
<string name="color">顏色</string>
|
||||
<string name="system_default">系統默認</string>
|
||||
<string name="system_default">系統</string>
|
||||
<string name="alarm">鬧鐘</string>
|
||||
<string name="light">亮色</string>
|
||||
<string name="dark">暗色</string>
|
||||
@@ -57,4 +57,17 @@
|
||||
<string name="timer">计时</string>
|
||||
<string name="timer_progress">计时进度</string>
|
||||
<string name="last_year">上年</string>
|
||||
<string name="always_on_display">熄屏模式</string>
|
||||
<string name="appearance">外觀</string>
|
||||
<string name="always_on_display_desc">在查看計時器時,點擊任意位置即可切換至熄屏模式</string>
|
||||
<string name="durations">持續時間</string>
|
||||
<string name="sound">聲音</string>
|
||||
<string name="dnd">請勿打擾</string>
|
||||
<string name="dnd_desc">在執行專注計時器時開啟勿擾模式</string>
|
||||
<string name="get_plus">獲取 Tomato+</string>
|
||||
<string name="app_name_plus">Tomato+</string>
|
||||
<string name="dynamic_color">動態顔色</string>
|
||||
<string name="dynamic_color_desc">調整主題顏色為你的壁紙顔色</string>
|
||||
<string name="tomato_foss">Tomato FOSS</string>
|
||||
<string name="tomato_foss_desc">所有功能在此版本中均已解鎖。如果我的應用程式改變了您的生活,請考慮透過捐贈 %1$s 來支持我。</string>
|
||||
</resources>
|
||||
|
||||
@@ -79,4 +79,12 @@
|
||||
<string name="appearance">Appearance</string>
|
||||
<string name="durations">Durations</string>
|
||||
<string name="sound">Sound</string>
|
||||
<string name="dnd">Do Not Disturb</string>
|
||||
<string name="dnd_desc">Turn on DND when running a Focus timer</string>
|
||||
<string name="app_name_plus">Tomato+</string>
|
||||
<string name="get_plus">Get Tomato+</string>
|
||||
<string name="dynamic_color">Dynamic color</string>
|
||||
<string name="dynamic_color_desc">Adapt theme colors from your wallpaper</string>
|
||||
<string name="tomato_foss">Tomato FOSS</string>
|
||||
<string name="tomato_foss_desc">All features are unlocked in this version. If my app made a difference in your life, please consider supporting me by donating on %1$s.</string>
|
||||
</resources>
|
||||
21
app/src/play/AndroidManifest.xml
Normal file
21
app/src/play/AndroidManifest.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2025 Nishant Mishra
|
||||
~
|
||||
~ This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
~
|
||||
~ Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
~ General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
~ License, or (at your option) any later version.
|
||||
~
|
||||
~ Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
~ the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
~ Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with Tomato.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="com.android.vending.BILLING" />
|
||||
</manifest>
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.billing
|
||||
|
||||
import android.util.Log
|
||||
import com.revenuecat.purchases.Purchases
|
||||
import com.revenuecat.purchases.getCustomerInfoWith
|
||||
import com.revenuecat.purchases.interfaces.UpdatedCustomerInfoListener
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
private const val ENTITLEMENT_ID = "plus"
|
||||
|
||||
/**
|
||||
* Google Play implementation of BillingManager
|
||||
*/
|
||||
class PlayBillingManager : BillingManager {
|
||||
private val _isPlus = MutableStateFlow(false)
|
||||
override val isPlus = _isPlus.asStateFlow()
|
||||
|
||||
private val _isLoaded = MutableStateFlow(false)
|
||||
override val isLoaded = _isLoaded.asStateFlow()
|
||||
|
||||
private val purchases by lazy { Purchases.sharedInstance }
|
||||
|
||||
init {
|
||||
purchases.updatedCustomerInfoListener =
|
||||
UpdatedCustomerInfoListener { customerInfo ->
|
||||
_isPlus.value = customerInfo.entitlements[ENTITLEMENT_ID]?.isActive == true
|
||||
}
|
||||
|
||||
// Fetch initial customer info
|
||||
purchases.getCustomerInfoWith(
|
||||
onSuccess = { customerInfo ->
|
||||
_isPlus.value = customerInfo.entitlements[ENTITLEMENT_ID]?.isActive == true
|
||||
_isLoaded.value = true
|
||||
},
|
||||
onError = { error ->
|
||||
Log.e("GooglePlayPaywallManager", "Error fetching customer info: $error")
|
||||
_isLoaded.value = true
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object BillingManagerProvider {
|
||||
val manager: BillingManager = PlayBillingManager()
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.billing
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.revenuecat.purchases.ui.revenuecatui.Paywall
|
||||
import com.revenuecat.purchases.ui.revenuecatui.PaywallOptions
|
||||
import com.revenuecat.purchases.ui.revenuecatui.customercenter.CustomerCenter
|
||||
import org.nsh07.pomodoro.R
|
||||
|
||||
@Composable
|
||||
fun TomatoPlusPaywallDialog(
|
||||
isPlus: Boolean,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
val paywallOptions = remember {
|
||||
PaywallOptions.Builder(dismissRequest = onDismiss).build()
|
||||
}
|
||||
|
||||
Scaffold { innerPadding ->
|
||||
if (!isPlus) {
|
||||
Paywall(paywallOptions)
|
||||
|
||||
FilledTonalIconButton(
|
||||
onClick = onDismiss,
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Icon(
|
||||
painterResource(R.drawable.arrow_back),
|
||||
null
|
||||
)
|
||||
}
|
||||
} else {
|
||||
CustomerCenter(onDismiss = onDismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Nishant Mishra
|
||||
*
|
||||
* This file is part of Tomato - a minimalist pomodoro timer for Android.
|
||||
*
|
||||
* Tomato is free software: you can redistribute it and/or modify it under the terms of the GNU
|
||||
* General Public License as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* Tomato is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
||||
* Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with Tomato.
|
||||
* If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.nsh07.pomodoro.billing
|
||||
|
||||
import android.content.Context
|
||||
import com.revenuecat.purchases.Purchases
|
||||
import com.revenuecat.purchases.PurchasesConfiguration
|
||||
|
||||
fun initializePurchases(context: Context) {
|
||||
Purchases.configure(
|
||||
PurchasesConfiguration
|
||||
.Builder(context, "goog_jBpRIBjTYvhKYluCqkPXSHbuFbX")
|
||||
.build()
|
||||
)
|
||||
}
|
||||
@@ -1 +1,12 @@
|
||||
<p><i>Tomato</i> ist ein minimalistischer Pomodoro-Timer für Android, der auf Material 3 Expressive basiert. </p><p><br><b>Funktionen:</b></p><ul><li>Einfache, minimalistische Benutzeroberfläche, die auf den neuesten Material 3 Expressive-Richtlinien basiert</li><li>Detaillierte Statistiken zu Arbeits-/Lernzeiten in leicht verständlicher Form<ul><li>Statistiken für den aktuellen Tag auf einen Blick sichtbar</li><li>Statistiken für die letzte Woche und den letzten Monat in einer übersichtlichen und sauberen Grafik dargestellt</li><li>Zusätzliche Statistiken für die letzte Woche und den letzten Monat zeigen, zu welcher Tageszeit Sie am produktivsten sind</li></ul></li><li>Anpassbare Timer Voreinstellungen</li></ul>
|
||||
<i>Tomato</i> ist ein minimalistischer Pomodoro-Timer für Android, der auf Material 3 Expressive basiert.
|
||||
|
||||
Tomato ist vollständig kostenlos und Open Source, für immer. Unter https://github.com/nsh07/Tomato finden Sie den Quellcode und können Fehler melden oder Funktionen vorschlagen
|
||||
|
||||
<b>Funktionen:</b>
|
||||
-Einfache, minimalistische Benutzeroberfläche, die auf den neuesten Material 3 Expressive-Richtlinien basiert
|
||||
-Detaillierte Statistiken zu Arbeits-/Lernzeiten in leicht verständlicher Form
|
||||
-Statistiken für den aktuellen Tag auf einen Blick sichtbar
|
||||
-Statistiken für die letzte Woche und den letzten Monat in einer übersichtlichen und sauberen Grafik dargestellt
|
||||
-Zusätzliche Statistiken für die letzte Woche und den letzten Monat zeigen, zu welcher Tageszeit Sie am produktivsten sind
|
||||
-Anpassbare Timer Voreinstellungen
|
||||
-Unterstützung für Android 16 Live-Updates
|
||||
|
||||
5
fastlane/metadata/android/en-US/changelogs/17.txt
Normal file
5
fastlane/metadata/android/en-US/changelogs/17.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
New features:
|
||||
- The app can now automatically turn on Do Not Disturb mode when a focus timer is running (this needs to be enabled in Settings)
|
||||
|
||||
And other fixes.
|
||||
This update also contains a new paywall system (For Google Play only)
|
||||
12
fastlane/metadata/android/hi-IN/full_description.txt
Normal file
12
fastlane/metadata/android/hi-IN/full_description.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
<i>टोमैटो</i> एंड्रॉइड के लिए मटेरियल 3 एक्सप्रेसिव पर आधारित एक न्यूनतम पोमोडोरो टाइमर है।
|
||||
|
||||
टोमैटो पूरी तरह से मुफ़्त और ओपन-सोर्स है, हमेशा के लिए। आप https://github.com/nsh07/Tomato पर सोर्स कोड पा सकते हैं, बग रिपोर्ट कर सकते हैं या सुविधाएँ सुझा सकते हैं।
|
||||
|
||||
<b>विशेषताएँ:</b>
|
||||
- नवीनतम मटेरियल 3 एक्सप्रेसिव दिशानिर्देशों पर आधारित सरल, न्यूनतम UI
|
||||
- समझने में आसान तरीके से काम/अध्ययन समय के विस्तृत आँकड़े
|
||||
- एक नज़र में वर्तमान दिन के आँकड़े देखें
|
||||
- पढ़ने में आसान, साफ़ ग्राफ़ में दिखाए गए पिछले हफ़्ते और पिछले महीने के आँकड़े देखें
|
||||
- पिछले हफ़्ते और महीने के अतिरिक्त आँकड़े जो दिखाते हैं कि दिन के किस समय आप सबसे ज़्यादा उत्पादक होते हैं
|
||||
- अनुकूलन योग्य टाइमर पैरामीटर
|
||||
- एंड्रॉइड 16 लाइव अपडेट
|
||||
@@ -1,12 +1,12 @@
|
||||
<i>Tomato</i> 是一个基于Material 3 Expressive的安卓极简主义番茄钟.
|
||||
<i>Tomato</i> 是一个基于Material 3 Expressive 的安卓极简主义番茄钟。
|
||||
|
||||
Tomato 将永远保持完全免费和开源。如果你想获取源代码、报告程序错误(bug)或建议新功能,请访问 https://github.com/nsh07/Tomato。
|
||||
Tomato 将永远保持完全免费和开源。如果你想获取源代码、报告程序错误(bug)或建议新功能,请访问 https://github.com/nsh07/Tomato
|
||||
|
||||
<b>功能:</b>
|
||||
- 基于最新Material 3 Expressive指南的简洁用户界面
|
||||
- 以便于理解的方式提供工作/学习的详细统计数据
|
||||
- 以便于理解的方式提供工作/学习时间的详细统计数据
|
||||
- 当日统计数据一目了然
|
||||
- 清楚易读的上周和上月统计图表
|
||||
- 上周和上月的额外统计数据帮您找到一天中最高效的时间段
|
||||
- 可自定义的计时器参数
|
||||
- 支持 Android 16 即時更新 (Android 16 Live Updates)
|
||||
- 支持 Android 16 即时更新 (Android 16 Live Updates)
|
||||
|
||||
12
fastlane/metadata/android/zh-TW/full_description.txt
Normal file
12
fastlane/metadata/android/zh-TW/full_description.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
<i>Tomato</i> 是一款基於 Material 3 Expressive 的 Android 極簡番茄鐘計時器。
|
||||
|
||||
Tomato 完全免費且開源,永遠如此。你可以在 https://github.com/nsh07/Tomato 取得原始碼,並回報問題或提出功能建議。
|
||||
|
||||
<b>功能:</b>
|
||||
- 依循最新 Material 3 Expressive 設計規範的簡潔、極簡介面
|
||||
- 以易懂方式呈現工作/讀書時間的詳細統計
|
||||
- 當日統計一目了然
|
||||
- 過去一週與一個月的統計,以清爽易讀的圖表呈現
|
||||
- 另外提供過去一週與一個月在一天中何時最有效率的統計
|
||||
- 計時器參數可自訂
|
||||
- 支援 Android 16 的 Live Updates
|
||||
1
fastlane/metadata/android/zh-TW/short_description.txt
Normal file
1
fastlane/metadata/android/zh-TW/short_description.txt
Normal file
@@ -0,0 +1 @@
|
||||
極簡番茄鐘
|
||||
@@ -12,6 +12,7 @@ ksp = "2.2.20-2.0.4"
|
||||
lifecycleRuntimeKtx = "2.9.4"
|
||||
materialKolor = "3.0.1"
|
||||
navigation3 = "1.0.0-beta01"
|
||||
revenuecat = "9.12.0"
|
||||
room = "2.8.3"
|
||||
vico = "2.2.1"
|
||||
|
||||
@@ -39,6 +40,8 @@ androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
material-kolor = { module = "com.materialkolor:material-kolor", version.ref = "materialKolor" }
|
||||
revenuecat-purchases = { module = "com.revenuecat.purchases:purchases", version.ref = "revenuecat" }
|
||||
revenuecat-purchases-ui = { module = "com.revenuecat.purchases:purchases-ui", version.ref = "revenuecat" }
|
||||
vico-compose-m3 = { group = "com.patrykandpatrick.vico", name = "compose-m3", version.ref = "vico" }
|
||||
|
||||
[plugins]
|
||||
|
||||
Reference in New Issue
Block a user