Skip to content
Snippets Groups Projects
Commit dd1bfca0 authored by Aleks Dormushev's avatar Aleks Dormushev
Browse files

Merge branch 'sings.v2' into 'main'

Sings.v2

See merge request !1
parents f53137cd 38fcdf0b
No related branches found
No related tags found
1 merge request!1Sings.v2
Showing
with 147 additions and 73 deletions
......@@ -5,9 +5,6 @@
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="MainActivity">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
......@@ -12,16 +17,20 @@
android:supportsRtl="true"
android:theme="@style/Theme.Timer2"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.Timer2">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.example.timer2.PomodoroTimerService"
android:foregroundServiceType="dataSync"/>
</application>
</manifest>
\ No newline at end of file
package com.example.timer2
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class PomodoroTimerService : Service() {
private var timerJob: Job? = null
private lateinit var notificationManager: NotificationManager
private val CHANNEL_ID = "PomodoroTimerChannel"
private val NOTIFICATION_ID = 1
override fun onCreate() {
super.onCreate()
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) {
"START" -> {
val duration = intent.getLongExtra("DURATION", 0L)
startForegroundService(duration)
}
"STOP" -> stopSelf()
}
return START_NOT_STICKY
}
private fun startForegroundService(duration: Long) {
val notification = createNotification("Pomodoro Timer Running", "Time left: ${formatTime(duration)}")
startForeground(NOTIFICATION_ID, notification)
timerJob = CoroutineScope(Dispatchers.Default).launch {
var timeLeft = duration
while (timeLeft > 0) {
delay(1000)
timeLeft -= 1000
updateNotification("Time left: ${formatTime(timeLeft)}")
}
stopSelf()
}
}
private fun createNotification(title: String, content: String): Notification {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle(title)
.setContentText(content)
.setSmallIcon(R.drawable.ic_notification) // Make sure to add this icon to your drawable resources
.setContentIntent(pendingIntent)
.build()
}
private fun updateNotification(content: String) {
val notification = createNotification("Pomodoro Timer Running", content)
notificationManager.notify(NOTIFICATION_ID, notification)
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Pomodoro Timer Channel",
NotificationManager.IMPORTANCE_LOW
)
notificationManager.createNotificationChannel(channel)
}
}
private fun formatTime(timeInMillis: Long): String {
val minutes = timeInMillis / 60000
val seconds = (timeInMillis % 60000) / 1000
return String.format("%02d:%02d", minutes, seconds)
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
super.onDestroy()
timerJob?.cancel()
}
}
\ No newline at end of file
......@@ -5,16 +5,12 @@ import android.content.SharedPreferences
class PreferencesHelper(context: Context) {
private val prefs: SharedPreferences = context.getSharedPreferences("PomodoroPrefs", Context.MODE_PRIVATE)
fun getPomodoroDuration(): Int = prefs.getInt("pomodoro_duration", 25 * 60) // Default 25 minutes
fun savePomodoroDuration(duration: Int) = prefs.edit().putInt("pomodoro_duration", duration).apply()
fun getDarkThemeEnabled(): Boolean = prefs.getBoolean("dark_theme_enabled", false)
fun saveDarkThemeEnabled(enabled: Boolean) = prefs.edit().putBoolean("dark_theme_enabled", enabled).apply()
fun getSoundEnabled(): Boolean = prefs.getBoolean("sound_enabled", true)
fun saveSoundEnabled(enabled: Boolean) = prefs.edit().putBoolean("sound_enabled", enabled).apply()
fun getVibrationEnabled(): Boolean = prefs.getBoolean("vibration_enabled", true)
fun saveVibrationEnabled(enabled: Boolean) = prefs.edit().putBoolean("vibration_enabled", enabled).apply()
}
\ No newline at end of file
......@@ -10,15 +10,3 @@ import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppBarWithSettings(navController: NavController) {
TopAppBar(
title = { Text("To-Do") },
actions = {
IconButton(onClick = { navController.navigate("settings") }) {
Icon(Icons.Default.Settings, contentDescription = "Settings")
}
}
)
}
......@@ -11,7 +11,6 @@ import androidx.navigation.compose.composable
import androidx.navigation.navArgument
import com.example.timer2.PreferencesHelper
import com.example.timer2.data.ToDoRepository
import com.example.timer2.viewmodel.SettingsViewModel
import com.example.timer2.viewmodel.ToDoViewModel
import com.example.timer2.viewmodel.ToDoViewModelFactory
......@@ -73,7 +72,7 @@ fun AppNavigation(
if (toDoItem != null) {
PomodoroTimerScreen(
navController = navController,
timerDuration = toDoItem.duration , // Change for testing !!!!!!!!!!
timerDuration = toDoItem.duration * 60 , // Change for testing !!!!!!!!!!
onTimerReset = { /* Logic for resetting the timer */ },
preferencesHelper = preferencesHelper,
selectedTimerName = toDoItem.name
......
package com.example.timer2.ui.theme
import androidx.compose.ui.graphics.Color
......@@ -15,6 +15,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
......@@ -53,6 +54,12 @@ fun PomodoroTimerScreen(
viewModel.setInitialDuration(timerDuration)
}
DisposableEffect(Unit) {
onDispose {
viewModel.resetTimer() // This will stop the foreground service when leaving the screen
}
}
Scaffold(
// ... (keep existing top bar)
) { paddingValues ->
......
......@@ -4,8 +4,3 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Shapes
import androidx.compose.ui.unit.dp
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(8.dp),
large = RoundedCornerShape(16.dp)
)
package com.example.timer2.viewmodel
import android.app.Application
import android.content.Intent
import android.media.MediaPlayer
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.util.Log
......@@ -10,6 +12,7 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.example.timer2.PomodoroTimerService
import com.example.timer2.PreferencesHelper
import com.example.timer2.R
import kotlinx.coroutines.Job
......@@ -31,7 +34,7 @@ enum class TimerState {
}
class PomodoroTimerViewModel(
application: Application,
private val application: Application,
private val preferencesHelper: PreferencesHelper
) : AndroidViewModel(application) {
......@@ -90,6 +93,8 @@ class PomodoroTimerViewModel(
if (_timerState.value == TimerState.RUNNING) return
_timerState.value = TimerState.RUNNING
startForegroundService(duration)
timerJob = viewModelScope.launch {
val endTime = System.currentTimeMillis() + duration
while (System.currentTimeMillis() < endTime && _timerState.value == TimerState.RUNNING) {
......@@ -100,6 +105,25 @@ class PomodoroTimerViewModel(
}
}
private fun startForegroundService(duration: Long) {
val intent = Intent(application, PomodoroTimerService::class.java).apply {
action = "START"
putExtra("DURATION", duration)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
application.startForegroundService(intent)
} else {
application.startService(intent)
}
}
private fun stopForegroundService() {
val intent = Intent(application, PomodoroTimerService::class.java).apply {
action = "STOP"
}
application.stopService(intent)
}
fun selectTimer(timerType: TimerType) {
_timerType.value = timerType
_currentTimerDuration.value = when (timerType) {
......@@ -144,6 +168,7 @@ class PomodoroTimerViewModel(
private fun pauseTimer() {
_timerState.value = TimerState.PAUSED
timerJob?.cancel()
stopForegroundService()
}
fun resetTimer() {
......@@ -152,6 +177,7 @@ class PomodoroTimerViewModel(
_timerType.value = TimerType.NORMAL
_currentTimerDuration.value = initialDuration
_timeLeft.value = initialDuration
stopForegroundService()
}
override fun onCleared() {
......
package com.example.timer2.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class SettingsViewModel : ViewModel() {
private val _pomodoroTime = MutableLiveData<Int>(25 * 60) // Default 25 minutes in seconds
val pomodoroTime: LiveData<Int> = _pomodoroTime
private val _isDarkMode = MutableLiveData<Boolean>(false)
val isDarkMode: LiveData<Boolean> = _isDarkMode
private val _isSoundEnabled = MutableLiveData<Boolean>(true)
val isSoundEnabled: LiveData<Boolean> = _isSoundEnabled
private val _isVibrationEnabled = MutableLiveData<Boolean>(true)
val isVibrationEnabled: LiveData<Boolean> = _isVibrationEnabled
private val _showInputDialog = MutableLiveData<Boolean>(false)
val showInputDialog: LiveData<Boolean> = _showInputDialog
fun setPomodoroTime(minutes: Int) {
_pomodoroTime.value = minutes * 60 // Convert minutes to seconds
}
fun setDarkMode(isDark: Boolean) {
_isDarkMode.value = isDark
}
fun setSoundEnabled(isEnabled: Boolean) {
_isSoundEnabled.value = isEnabled
}
fun setVibrationEnabled(isEnabled: Boolean) {
_isVibrationEnabled.value = isEnabled
}
fun toggleInputDialog(show: Boolean) {
_showInputDialog.value = show
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
</selector>
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment