diff --git a/android/app/build.gradle b/android/app/build.gradle index 6fdc358..b4507c1 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -131,6 +131,9 @@ android { } } } + buildFeatures { + viewBinding true + } buildTypes { debug { signingConfig signingConfigs.debug @@ -144,6 +147,8 @@ android { } dependencies { + implementation 'androidx.appcompat:appcompat:1.4.1' + implementation 'com.google.android.material:material:1.4.+' def work_version = "2.7.1" implementation "androidx.work:work-runtime:$work_version" diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f0e8b7a..58511a8 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,3 +1,4 @@ + @@ -7,16 +8,30 @@ - + + android:theme="@style/AppTheme"> + + + - - - - + + + + + - + + \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/Fullscreen.kt b/android/app/src/main/java/com/massive/Fullscreen.kt new file mode 100644 index 0000000..d64e4bf --- /dev/null +++ b/android/app/src/main/java/com/massive/Fullscreen.kt @@ -0,0 +1,185 @@ +package com.massive + +import androidx.appcompat.app.AppCompatActivity +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.view.WindowInsets +import android.widget.LinearLayout +import android.widget.TextView +import com.massive.databinding.ActivityFullscreenBinding + +/** + * An example full-screen activity that shows and hides the system UI (i.e. + * status bar and navigation/system bar) with user interaction. + */ +class Fullscreen : AppCompatActivity() { + + private lateinit var binding: ActivityFullscreenBinding + private lateinit var fullscreenContent: TextView + private lateinit var fullscreenContentControls: LinearLayout + private val hideHandler = Handler(Looper.myLooper()!!) + + @SuppressLint("InlinedApi") + private val hidePart2Runnable = Runnable { + // Delayed removal of status and navigation bar + if (Build.VERSION.SDK_INT >= 30) { + fullscreenContent.windowInsetsController?.hide(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) + } else { + // Note that some of these constants are new as of API 16 (Jelly Bean) + // and API 19 (KitKat). It is safe to use them, as they are inlined + // at compile-time and do nothing on earlier devices. + fullscreenContent.systemUiVisibility = + View.SYSTEM_UI_FLAG_LOW_PROFILE or + View.SYSTEM_UI_FLAG_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + } + } + private val showPart2Runnable = Runnable { + // Delayed display of UI elements + supportActionBar?.show() + fullscreenContentControls.visibility = View.VISIBLE + } + private var isFullscreen: Boolean = false + + private val hideRunnable = Runnable { hide() } + + /** + * Touch listener to use for in-layout UI controls to delay hiding the + * system UI. This is to prevent the jarring behavior of controls going away + * while interacting with activity UI. + */ + private val delayHideTouchListener = View.OnTouchListener { view, motionEvent -> + when (motionEvent.action) { + MotionEvent.ACTION_DOWN -> if (AUTO_HIDE) { + delayedHide(AUTO_HIDE_DELAY_MILLIS) + } + MotionEvent.ACTION_UP -> view.performClick() + else -> { + } + } + false + } + + private fun stop() { + Log.d("Fullscreen", "Stopping...") + applicationContext.stopService(Intent(applicationContext, TimerService::class.java)) + applicationContext.stopService(Intent(applicationContext, AlarmService::class.java)) + val intent = Intent(applicationContext, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + applicationContext.startActivity(intent) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityFullscreenBinding.inflate(layoutInflater) + setContentView(binding.root) + + supportActionBar?.setDisplayHomeAsUpEnabled(true) + + isFullscreen = true + + // Set up the user interaction to manually show or hide the system UI. + fullscreenContent = binding.fullscreenContent + fullscreenContent.setOnClickListener { toggle() } + + fullscreenContentControls = binding.fullscreenContentControls + + // Upon interacting with UI controls, delay any scheduled hide() + // operations to prevent the jarring behavior of controls going away + // while interacting with the UI. + binding.dummyButton.setOnTouchListener(delayHideTouchListener) + + binding.dummyButton.setOnClickListener { + stop() + } + + binding.fullscreenContent.setOnClickListener { + stop() + } + } + + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + + // Trigger the initial hide() shortly after the activity has been + // created, to briefly hint to the user that UI controls + // are available. + delayedHide(100) + } + + private fun toggle() { + if (isFullscreen) { + hide() + } else { + show() + } + } + + private fun hide() { + // Hide UI first + supportActionBar?.hide() + fullscreenContentControls.visibility = View.GONE + isFullscreen = false + + // Schedule a runnable to remove the status and navigation bar after a delay + hideHandler.removeCallbacks(showPart2Runnable) + hideHandler.postDelayed(hidePart2Runnable, UI_ANIMATION_DELAY.toLong()) + } + + private fun show() { + // Show the system bar + if (Build.VERSION.SDK_INT >= 30) { + fullscreenContent.windowInsetsController?.show(WindowInsets.Type.statusBars() or WindowInsets.Type.navigationBars()) + } else { + fullscreenContent.systemUiVisibility = + View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + } + isFullscreen = true + + // Schedule a runnable to display UI elements after a delay + hideHandler.removeCallbacks(hidePart2Runnable) + hideHandler.postDelayed(showPart2Runnable, UI_ANIMATION_DELAY.toLong()) + } + + /** + * Schedules a call to hide() in [delayMillis], canceling any + * previously scheduled calls. + */ + private fun delayedHide(delayMillis: Int) { + hideHandler.removeCallbacks(hideRunnable) + hideHandler.postDelayed(hideRunnable, delayMillis.toLong()) + } + + companion object { + /** + * Whether or not the system UI should be auto-hidden after + * [AUTO_HIDE_DELAY_MILLIS] milliseconds. + */ + private const val AUTO_HIDE = true + + /** + * If [AUTO_HIDE] is set, the number of milliseconds to wait after + * user interaction before hiding the system UI. + */ + private const val AUTO_HIDE_DELAY_MILLIS = 3000 + + /** + * Some older devices needs a small delay between UI widget updates + * and a change of the status and navigation bar. + */ + private const val UI_ANIMATION_DELAY = 300 + } +} \ No newline at end of file diff --git a/android/app/src/main/java/com/massive/TimerService.kt b/android/app/src/main/java/com/massive/TimerService.kt index edd829b..0ce3397 100644 --- a/android/app/src/main/java/com/massive/TimerService.kt +++ b/android/app/src/main/java/com/massive/TimerService.kt @@ -73,12 +73,20 @@ class TimerService : Service() { finishIntent, PendingIntent.FLAG_IMMUTABLE ) + val fullIntent = Intent(applicationContext, Fullscreen::class.java) + val fullPending = + PendingIntent.getActivity( + applicationContext, + 0, + fullIntent, + PendingIntent.FLAG_IMMUTABLE + ) builder.setContentText("Timer finished.") .setProgress(0, 0, false) .setAutoCancel(true) .setOngoing(true) + .setFullScreenIntent(fullPending, true) .setContentIntent(finishPending) - .setFullScreenIntent(finishPending, true) .setChannelId(CHANNEL_ID_DONE) .setCategory(NotificationCompat.CATEGORY_ALARM) .priority = NotificationCompat.PRIORITY_HIGH diff --git a/android/app/src/main/res/layout/activity_fullscreen.xml b/android/app/src/main/res/layout/activity_fullscreen.xml new file mode 100644 index 0000000..f3f0f64 --- /dev/null +++ b/android/app/src/main/res/layout/activity_fullscreen.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + +