diff --git a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt index 86b047624..3114284f0 100644 --- a/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt +++ b/app/src/main/java/com/sameerasw/essentials/data/repository/SettingsRepository.kt @@ -1159,12 +1159,14 @@ class SettingsRepository(private val context: Context) { if (fixed <= 0f && min <= 0f && peak <= 0f) { com.sameerasw.essentials.utils.RefreshRateUtils.resetRefreshRate( + context, shouldRestoreInfinityPeakOnRefreshRateReset() ) } else if (mode == com.sameerasw.essentials.utils.RefreshRateUtils.MODE_RANGE && min > 0f && peak > 0f) { - com.sameerasw.essentials.utils.RefreshRateUtils.applyRangeRefreshRate(min, peak) + com.sameerasw.essentials.utils.RefreshRateUtils.applyRangeRefreshRate(context, min, peak) } else if (fixed > 0f || peak > 0f) { com.sameerasw.essentials.utils.RefreshRateUtils.applyFixedRefreshRate( + context, if (fixed > 0f) fixed else peak ) } diff --git a/app/src/main/java/com/sameerasw/essentials/input/VolumeLongPressDetector.kt b/app/src/main/java/com/sameerasw/essentials/input/VolumeLongPressDetector.kt index 996911a6a..9b6972e7d 100644 --- a/app/src/main/java/com/sameerasw/essentials/input/VolumeLongPressDetector.kt +++ b/app/src/main/java/com/sameerasw/essentials/input/VolumeLongPressDetector.kt @@ -18,6 +18,8 @@ class VolumeLongPressDetector( val events = _events.asSharedFlow() private val isRunning = AtomicBoolean(false) + val isListening: Boolean + get() = isRunning.get() private var detectionJob: Job? = null private val reader = InputEventReader(volumeDevicePath) diff --git a/app/src/main/java/com/sameerasw/essentials/services/InputEventListenerService.kt b/app/src/main/java/com/sameerasw/essentials/services/InputEventListenerService.kt index 02d6edd9d..470892e85 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/InputEventListenerService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/InputEventListenerService.kt @@ -10,11 +10,13 @@ import com.sameerasw.essentials.input.InputDeviceScanner import com.sameerasw.essentials.input.VolumeLongPressDetector import com.sameerasw.essentials.input.VolumePressEvent import com.sameerasw.essentials.shizuku.ShizukuPermissionHelper +import rikka.shizuku.Shizuku import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -31,6 +33,16 @@ class InputEventListenerService : Service() { private lateinit var shizukuHelper: ShizukuPermissionHelper private var isTorchOn = false + private val binderReceivedListener = Shizuku.OnBinderReceivedListener { + Log.d("InputEventListener", "Shizuku binder received, attempting to start listening") + startListening() + } + + private val binderDeadListener = Shizuku.OnBinderDeadListener { + Log.w("InputEventListener", "Shizuku binder died, stopping listener") + stopListening() + } + override fun onCreate() { super.onCreate() shizukuHelper = ShizukuPermissionHelper(this) @@ -73,6 +85,9 @@ class InputEventListenerService : Service() { startForeground(NOTIFICATION_ID, notification) } + Shizuku.addBinderReceivedListener(binderReceivedListener) + Shizuku.addBinderDeadListener(binderDeadListener) + scope?.launch { delay(500) startListening() @@ -81,37 +96,35 @@ class InputEventListenerService : Service() { private fun startListening() { scope?.launch { + stopListening() + if (!com.sameerasw.essentials.utils.ShellUtils.hasPermission(this@InputEventListenerService)) { Log.e("InputEventListener", "Shell permission not granted") return@launch } val devices = withContext(Dispatchers.IO) { - // find device InputDeviceScanner().scanForVolumeDevices() } if (devices.isEmpty()) { Log.e("InputEventListener", "No devices found") - // Clear prefs if no device found getSharedPreferences("essentials_prefs", MODE_PRIVATE) .edit().remove("shizuku_detected_device_path").apply() return@launch } - // picked first item - // IMPROVEMENT: Iterate and try to find the one that actually emits keys val devicePath = devices.first().path Log.d("InputEventListener", "Listening on device: $devicePath") - // Save detected device to prefs for UI getSharedPreferences("essentials_prefs", MODE_PRIVATE) .edit().putString("shizuku_detected_device_path", devicePath).apply() - detector = VolumeLongPressDetector(devicePath, 500) + val activeDetector = VolumeLongPressDetector(devicePath, 500) + detector = activeDetector launch { - detector?.events?.collect { event -> + activeDetector.events.collect { event -> val powerManager = getSystemService(POWER_SERVICE) as android.os.PowerManager val isScreenInteractive = try { @@ -121,7 +134,6 @@ class InputEventListenerService : Service() { } val isAod = isAodShowing() - // Only process volume events if screen is fully off (not interactive AND not AOD) if (isScreenInteractive || isAod) { return@collect } @@ -144,7 +156,6 @@ class InputEventListenerService : Service() { val isGlobalEnabled = prefs.getBoolean("flashlight_global_enabled", false) - // Only if adjustment is enabled if (isAdjustEnabled || isGlobalEnabled) { val action = if (event.direction == com.sameerasw.essentials.input.VolumeDirection.UP) @@ -207,10 +218,15 @@ class InputEventListenerService : Service() { } } - detector?.startListening(this) + activeDetector.startListening(this) } } + private fun stopListening() { + detector?.stopListening() + detector = null + } + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // Reinforce foreground val notification = NotificationCompat.Builder(this, "service_channel") @@ -231,7 +247,9 @@ class InputEventListenerService : Service() { } override fun onDestroy() { - detector?.stopListening() + Shizuku.removeBinderReceivedListener(binderReceivedListener) + Shizuku.removeBinderDeadListener(binderDeadListener) + stopListening() scope?.cancel() super.onDestroy() } diff --git a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt index 093daf239..983348501 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/NotificationListener.kt @@ -82,11 +82,23 @@ class NotificationListener : NotificationListenerService() { return false } + private fun isSilentNotification(sbn: StatusBarNotification): Boolean { + try { + val rankingMap = currentRanking ?: return false + val ranking = Ranking() + if (rankingMap.getRanking(sbn.key, ranking)) { + return ranking.importance < android.app.NotificationManager.IMPORTANCE_DEFAULT + } + } catch (_: Exception) { + } + return false + } + private fun populateActiveUnreadNotifications() { unreadNotifications.clear() try { activeNotifications?.forEach { sbn -> - if (!sbn.isOngoing && sbn.packageName != packageName && !isMediaNotification(sbn)) { + if (!sbn.isOngoing && sbn.packageName != packageName && !isMediaNotification(sbn) && !isSilentNotification(sbn)) { unreadNotifications[sbn.key] = sbn.packageName } } @@ -674,7 +686,7 @@ class NotificationListener : NotificationListenerService() { val pm = getSystemService(Context.POWER_SERVICE) as PowerManager val isReallyLocked = isScreenLocked || !pm.isInteractive || com.sameerasw.essentials.services.dreams.AmbientDreamService.isDreaming - if (isReallyLocked && !sbn.isOngoing && sbn.packageName != packageName && !isMediaNotification(sbn)) { + if (isReallyLocked && !sbn.isOngoing && sbn.packageName != packageName && !isMediaNotification(sbn) && !isSilentNotification(sbn)) { unreadNotifications[sbn.key] = sbn.packageName // Trigger refresh if something is playing try { diff --git a/app/src/main/java/com/sameerasw/essentials/services/dreams/AmbientDreamService.kt b/app/src/main/java/com/sameerasw/essentials/services/dreams/AmbientDreamService.kt index 4a7ea1a92..ed7c4c813 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/dreams/AmbientDreamService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/dreams/AmbientDreamService.kt @@ -26,6 +26,7 @@ import android.widget.TextClock import android.widget.TextView import androidx.core.content.res.ResourcesCompat import com.sameerasw.essentials.R +import com.sameerasw.essentials.services.NotificationListener import java.io.File import java.util.Random import kotlin.math.abs @@ -77,6 +78,7 @@ class AmbientDreamService : DreamService() { private var likeStatusView: ImageView? = null private var volumeIconView: ImageView? = null private var volumeStrokeView: VolumeStrokeView? = null + private var bottomVolumeProgressView: BottomVolumeProgressView? = null private var currentShapePath: Path? = null @@ -128,6 +130,7 @@ class AmbientDreamService : DreamService() { private val volumeHideRunnable = Runnable { volumeStrokeView?.animate()?.alpha(0f)?.setDuration(500)?.start() + bottomVolumeProgressView?.animate()?.alpha(0f)?.setDuration(500)?.start() } private val burnInProtectionRunnable = object : Runnable { @@ -453,11 +456,16 @@ class AmbientDreamService : DreamService() { val perc = (current.toFloat() / max.toFloat() * 100).toInt() if (isMusicMode) { - volumeStrokeView?.setColor(Color.WHITE) - volumeStrokeView?.updatePercentage(perc) + val isFill = getAlbumArtMode() == "fill" + if (isFill) { + bottomVolumeProgressView?.updatePercentage(perc) + bottomVolumeProgressView?.animate()?.alpha(1f)?.setDuration(300)?.start() + } else { + volumeStrokeView?.setColor(Color.WHITE) + volumeStrokeView?.updatePercentage(perc) + volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() + } - // Show and schedule hide - volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() handler.removeCallbacks(volumeHideRunnable) handler.postDelayed(volumeHideRunnable, 3000) @@ -493,6 +501,26 @@ class AmbientDreamService : DreamService() { } parentInfo.addView(notificationIconsLayout) + val audioManager = getSystemService(AUDIO_SERVICE) as? android.media.AudioManager + val initialPerc = audioManager?.let { + val current = it.getStreamVolume(android.media.AudioManager.STREAM_MUSIC) + val max = it.getStreamMaxVolume(android.media.AudioManager.STREAM_MUSIC) + (current.toFloat() / max.toFloat() * 100).toInt() + } ?: 0 + + bottomVolumeProgressView = BottomVolumeProgressView(this).apply { + layoutParams = FrameLayout.LayoutParams( + dpToPx(240f), + dpToPx(20f), + Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + ).apply { + bottomMargin = dpToPx(30f) + } + updatePercentage(initialPerc) + alpha = 0f + } + parentInfo.addView(bottomVolumeProgressView) + // Hide music elements initially centerContainer?.alpha = 0f textContainer?.alpha = 0f @@ -533,6 +561,8 @@ class AmbientDreamService : DreamService() { centerContainer?.animate()?.cancel() textContainer?.animate()?.cancel() volumeStrokeView?.cleanup() + bottomVolumeProgressView?.cleanup() + bottomVolumeProgressView = null } private fun handleIntent(intent: Intent) { @@ -550,6 +580,24 @@ class AmbientDreamService : DreamService() { return } + if (!isMusicMode) { + val mediaSessionManager = getSystemService(MEDIA_SESSION_SERVICE) as MediaSessionManager + val componentName = android.content.ComponentName( + this, + NotificationListener::class.java + ) + val sessions = try { + mediaSessionManager.getActiveSessions(componentName) + } catch (e: Exception) { + emptyList() + } + val anyPlaying = sessions.any { it.playbackState?.state == android.media.session.PlaybackState.STATE_PLAYING } + if (!anyPlaying) { + switchToClockMode() + return + } + } + eventType = intent.getStringExtra("event_type") targetPackage = intent.getStringExtra("package_name") val newTitle = intent.getStringExtra("track_title") @@ -577,18 +625,24 @@ class AmbientDreamService : DreamService() { updateNotificationIcons(unreadPackages) if (eventType == "volume") { - if (volumeKey == 24) { - volumeIconView?.setImageResource(R.drawable.rounded_volume_up_24) - } else if (volumeKey == 25) { - volumeIconView?.setImageResource(R.drawable.rounded_volume_down_24) - } - volumeIconView?.animate()?.alpha(1f)?.setDuration(200)?.start() + val isFill = getAlbumArtMode() == "fill" + if (isFill) { + bottomVolumeProgressView?.updatePercentage(volumePercentage) + bottomVolumeProgressView?.animate()?.alpha(1f)?.setDuration(300)?.start() + } else { + if (volumeKey == 24) { + volumeIconView?.setImageResource(R.drawable.rounded_volume_up_24) + } else if (volumeKey == 25) { + volumeIconView?.setImageResource(R.drawable.rounded_volume_down_24) + } + volumeIconView?.animate()?.alpha(1f)?.setDuration(200)?.start() - volumeStrokeView?.setColor(Color.WHITE) - volumeStrokeView?.updatePercentage(volumePercentage) + volumeStrokeView?.setColor(Color.WHITE) + volumeStrokeView?.updatePercentage(volumePercentage) + volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() + } // Show and schedule hide - volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() handler.removeCallbacks(volumeHideRunnable) handler.postDelayed(volumeHideRunnable, 3000) @@ -775,6 +829,7 @@ class AmbientDreamService : DreamService() { backgroundImageView?.animate()?.alpha(0f)?.setDuration(300)?.start() backgroundNextImageView?.animate()?.alpha(0f)?.setDuration(300)?.start() backgroundScrim?.animate()?.alpha(0f)?.setDuration(300)?.start() + bottomVolumeProgressView?.animate()?.alpha(0f)?.setDuration(300)?.start() } private fun updateMetadata(directBitmap: android.graphics.Bitmap? = null) { @@ -1162,4 +1217,91 @@ class AmbientDreamService : DreamService() { canvas.restore() } } + + private inner class BottomVolumeProgressView(context: Context) : View(context) { + private var currentPercentage: Float = 0f + private var animator: android.animation.ValueAnimator? = null + private var waveAnimator: android.animation.ValueAnimator? = null + private var phaseShift = 0f + + private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.WHITE + style = Paint.Style.STROKE + strokeWidth = dpToPx(3f).toFloat() + strokeCap = Paint.Cap.ROUND + } + private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = 0x33FFFFFF + style = Paint.Style.STROKE + strokeWidth = dpToPx(3f).toFloat() + strokeCap = Paint.Cap.ROUND + } + + private val path = Path() + private val trackPath = Path() + + init { + waveAnimator = android.animation.ValueAnimator.ofFloat(0f, (2 * Math.PI).toFloat()).apply { + duration = 1500 + repeatCount = android.animation.ValueAnimator.INFINITE + interpolator = android.view.animation.LinearInterpolator() + addUpdateListener { + phaseShift = it.animatedValue as Float + invalidate() + } + start() + } + } + + fun updatePercentage(newPercentage: Int) { + animator?.cancel() + animator = android.animation.ValueAnimator.ofFloat(currentPercentage, newPercentage.toFloat()).apply { + duration = 300 + interpolator = android.view.animation.DecelerateInterpolator() + addUpdateListener { + currentPercentage = it.animatedValue as Float + invalidate() + } + start() + } + } + + fun cleanup() { + animator?.cancel() + waveAnimator?.cancel() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + val w = width.toFloat() + val h = height.toFloat() + val centerY = h / 2f + + // Track (straight line) + trackPath.reset() + trackPath.moveTo(0f, centerY) + trackPath.lineTo(w, centerY) + canvas.drawPath(trackPath, trackPaint) + + // Progress Wavy Line + val progressWidth = w * (currentPercentage / 100f) + if (progressWidth > 0f) { + path.reset() + path.moveTo(0f, centerY) + + val waveLength = dpToPx(24f).toFloat() + val amplitude = dpToPx(3f).toFloat() + val frequency = (2 * Math.PI) / waveLength + + var x = 0f + val step = dpToPx(1f).toFloat() + while (x <= progressWidth) { + val y = centerY + amplitude * kotlin.math.sin(frequency * x - phaseShift).toFloat() + path.lineTo(x, y) + x += step + } + canvas.drawPath(path, paint) + } + } + } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/handlers/AmbientGlanceHandler.kt b/app/src/main/java/com/sameerasw/essentials/services/handlers/AmbientGlanceHandler.kt index 4640af049..e4ff798e4 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/handlers/AmbientGlanceHandler.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/handlers/AmbientGlanceHandler.kt @@ -27,6 +27,7 @@ import android.widget.TextClock import android.widget.TextView import androidx.core.content.res.ResourcesCompat import com.sameerasw.essentials.R +import com.sameerasw.essentials.services.NotificationListener import com.sameerasw.essentials.services.tiles.ScreenOffAccessibilityService import java.io.File import java.util.Random @@ -38,12 +39,14 @@ class AmbientGlanceHandler( private var overlayView: View? = null private var volumeStrokeView: VolumeStrokeView? = null private var volumeIconView: ImageView? = null + private var bottomVolumeProgressView: BottomVolumeProgressView? = null private var likeStatusView: ImageView? = null private var volumeReceiver: BroadcastReceiver? = null private val handler = Handler(Looper.getMainLooper()) private val volumeHideRunnable = Runnable { volumeStrokeView?.animate()?.alpha(0f)?.setDuration(500)?.start() + bottomVolumeProgressView?.animate()?.alpha(0f)?.setDuration(500)?.start() } private val temporaryHideRunnable = Runnable { @@ -127,8 +130,11 @@ class AmbientGlanceHandler( sessions.any { it.playbackState?.state == android.media.session.PlaybackState.STATE_PLAYING } if (!anyPlaying) { - fadeOutAndRemove() + handler.removeCallbacks(pauseDismissRunnable) + handler.postDelayed(pauseDismissRunnable, 3000L) return + } else { + handler.removeCallbacks(pauseDismissRunnable) } handler.postDelayed(this, 1000L) @@ -168,6 +174,10 @@ class AmbientGlanceHandler( fadeOutAndRemove() } + private val pauseDismissRunnable = Runnable { + fadeOutAndRemove() + } + fun handleIntent(intent: Intent) { if (intent.action == "HIDE_AMBIENT_GLANCE_TEMPORARILY") { if (overlayView != null && overlayView?.alpha != 0f) { @@ -185,9 +195,41 @@ class AmbientGlanceHandler( } val isPlaying = intent.getBooleanExtra("is_playing", true) + val isDock = intent.getBooleanExtra("is_docked_mode", false) || isDockedMode + val isTrackChange = intent.getStringExtra("event_type") == "track_change" + val earlyTitle = intent.getStringExtra("track_title") + val titleChanged = earlyTitle != null && earlyTitle != trackTitle + + if (isPlaying || isTrackChange || titleChanged) { + handler.removeCallbacks(pauseDismissRunnable) + } + if (!isPlaying) { - if (overlayView != null) fadeOutAndRemove() - return + if (!isTrackChange && !titleChanged) { + if (overlayView != null) { + handler.removeCallbacks(pauseDismissRunnable) + handler.postDelayed(pauseDismissRunnable, 3000L) + } else { + return + } + } + } + + if (overlayView == null && !isDock) { + val mediaSessionManager = + service.getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager + val componentName = + android.content.ComponentName(service, NotificationListener::class.java) + val sessions = try { + mediaSessionManager.getActiveSessions(componentName) + } catch (e: Exception) { + emptyList() + } + val anyPlaying = + sessions.any { it.playbackState?.state == android.media.session.PlaybackState.STATE_PLAYING } + if (!anyPlaying) { + return + } } eventType = intent.getStringExtra("event_type") val newTitle = intent.getStringExtra("track_title") @@ -229,13 +271,16 @@ class AmbientGlanceHandler( // Volume key icon update helper if (eventType == EVENT_VOLUME) { - if (volumeKey == 24) volumeIconView?.setImageResource(R.drawable.rounded_volume_up_24) - else if (volumeKey == 25) volumeIconView?.setImageResource(R.drawable.rounded_volume_down_24) - volumeIconView?.animate()?.alpha(1f)?.setDuration(200)?.start() - volumeStrokeView?.setColor(Color.WHITE) - - // Show and schedule hide - volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() + val isFill = getAlbumArtMode() == "fill" + if (isFill) { + bottomVolumeProgressView?.animate()?.alpha(1f)?.setDuration(300)?.start() + } else { + if (volumeKey == 24) volumeIconView?.setImageResource(R.drawable.rounded_volume_up_24) + else if (volumeKey == 25) volumeIconView?.setImageResource(R.drawable.rounded_volume_down_24) + volumeIconView?.animate()?.alpha(1f)?.setDuration(200)?.start() + volumeStrokeView?.setColor(Color.WHITE) + volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() + } handler.removeCallbacks(volumeHideRunnable) handler.postDelayed(volumeHideRunnable, 3000) } @@ -257,7 +302,13 @@ class AmbientGlanceHandler( if (eventType == EVENT_VOLUME) { handler.removeCallbacks(progressUpdateRunnable) handler.removeCallbacks(revertToMusicRunnable) - volumeStrokeView?.updatePercentage(volumePercentage) + + val isFill = getAlbumArtMode() == "fill" + if (isFill) { + bottomVolumeProgressView?.updatePercentage(volumePercentage) + } else { + volumeStrokeView?.updatePercentage(volumePercentage) + } if (isDockedMode) { handler.postDelayed(revertToMusicRunnable, DISPLAY_DURATION) @@ -771,10 +822,16 @@ class AmbientGlanceHandler( it.getStreamVolume(android.media.AudioManager.STREAM_MUSIC) val max = it.getStreamMaxVolume(android.media.AudioManager.STREAM_MUSIC) val perc = (current.toFloat() / max.toFloat() * 100).toInt() - volumeStrokeView?.updatePercentage(perc) - // Show and schedule hide - volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() + val mode = getAlbumArtMode() + if (mode == "fill") { + bottomVolumeProgressView?.updatePercentage(perc) + bottomVolumeProgressView?.animate()?.alpha(1f)?.setDuration(300)?.start() + } else { + volumeStrokeView?.updatePercentage(perc) + volumeStrokeView?.animate()?.alpha(1f)?.setDuration(300)?.start() + } + handler.removeCallbacks(volumeHideRunnable) handler.postDelayed(volumeHideRunnable, 3000) } @@ -787,7 +844,6 @@ class AmbientGlanceHandler( ) } - // 4. Notification Icons at bottom notificationIconsLayout = android.widget.LinearLayout(context).apply { layoutParams = FrameLayout.LayoutParams( @@ -806,6 +862,20 @@ class AmbientGlanceHandler( } rootLayout.addView(notificationIconsLayout) + val isFill = getAlbumArtMode() == "fill" + bottomVolumeProgressView = BottomVolumeProgressView(context).apply { + layoutParams = FrameLayout.LayoutParams( + dpToPx(240f), + dpToPx(20f), + Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL + ).apply { + bottomMargin = dpToPx(30f) + } + updatePercentage(initialPerc) + alpha = if (eventType == EVENT_VOLUME && isFill) 1f else 0f + } + rootLayout.addView(bottomVolumeProgressView) + overlayView = rootLayout overlayView?.alpha = 0f @@ -976,6 +1046,7 @@ class AmbientGlanceHandler( fun removeOverlay() { handler.removeCallbacks(hideRunnable) + handler.removeCallbacks(pauseDismissRunnable) handler.removeCallbacks(progressUpdateRunnable) handler.removeCallbacks(revertToMusicRunnable) handler.removeCallbacks(burnInProtectionRunnable) @@ -997,9 +1068,11 @@ class AmbientGlanceHandler( centerContainer?.animate()?.cancel() textContainer?.animate()?.cancel() volumeStrokeView?.cleanup() + bottomVolumeProgressView?.cleanup() overlayView = null volumeStrokeView = null + bottomVolumeProgressView = null volumeIconView = null likeStatusView = null notificationIconsLayout = null @@ -1120,4 +1193,91 @@ class AmbientGlanceHandler( canvas.restore() } } + + private inner class BottomVolumeProgressView(context: Context) : View(context) { + private var currentPercentage: Float = 0f + private var animator: android.animation.ValueAnimator? = null + private var waveAnimator: android.animation.ValueAnimator? = null + private var phaseShift = 0f + + private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.WHITE + style = Paint.Style.STROKE + strokeWidth = dpToPx(3f).toFloat() + strokeCap = Paint.Cap.ROUND + } + private val trackPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = 0x33FFFFFF + style = Paint.Style.STROKE + strokeWidth = dpToPx(3f).toFloat() + strokeCap = Paint.Cap.ROUND + } + + private val path = Path() + private val trackPath = Path() + + init { + waveAnimator = android.animation.ValueAnimator.ofFloat(0f, (2 * Math.PI).toFloat()).apply { + duration = 1500 + repeatCount = android.animation.ValueAnimator.INFINITE + interpolator = android.view.animation.LinearInterpolator() + addUpdateListener { + phaseShift = it.animatedValue as Float + invalidate() + } + start() + } + } + + fun updatePercentage(newPercentage: Int) { + animator?.cancel() + animator = android.animation.ValueAnimator.ofFloat(currentPercentage, newPercentage.toFloat()).apply { + duration = 300 + interpolator = android.view.animation.DecelerateInterpolator() + addUpdateListener { + currentPercentage = it.animatedValue as Float + invalidate() + } + start() + } + } + + fun cleanup() { + animator?.cancel() + waveAnimator?.cancel() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + val w = width.toFloat() + val h = height.toFloat() + val centerY = h / 2f + + // Track (straight line) + trackPath.reset() + trackPath.moveTo(0f, centerY) + trackPath.lineTo(w, centerY) + canvas.drawPath(trackPath, trackPaint) + + // Progress Wavy Line + val progressWidth = w * (currentPercentage / 100f) + if (progressWidth > 0f) { + path.reset() + path.moveTo(0f, centerY) + + val waveLength = dpToPx(24f).toFloat() + val amplitude = dpToPx(3f).toFloat() + val frequency = (2 * Math.PI) / waveLength + + var x = 0f + val step = dpToPx(1f).toFloat() + while (x <= progressWidth) { + val y = centerY + amplitude * kotlin.math.sin(frequency * x - phaseShift).toFloat() + path.lineTo(x, y) + x += step + } + canvas.drawPath(path, paint) + } + } + } } diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/RefreshRateTileService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/RefreshRateTileService.kt index 2db8aca4e..4ae6a1911 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/RefreshRateTileService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/RefreshRateTileService.kt @@ -9,6 +9,7 @@ import com.sameerasw.essentials.FeatureSettingsActivity import com.sameerasw.essentials.R import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.utils.RefreshRateUtils +import com.sameerasw.essentials.utils.ShellUtils import com.sameerasw.essentials.utils.ShizukuUtils @RequiresApi(Build.VERSION_CODES.N) @@ -42,10 +43,11 @@ class RefreshRateTileService : BaseTileService() { if (nextPreset <= 0) { val settingsRepository = SettingsRepository(this) RefreshRateUtils.resetRefreshRate( + this, settingsRepository.shouldRestoreInfinityPeakOnRefreshRateReset() ) } else { - RefreshRateUtils.applyFixedRefreshRate(nextPreset.toFloat()) + RefreshRateUtils.applyFixedRefreshRate(this, nextPreset.toFloat()) } } @@ -53,7 +55,7 @@ class RefreshRateTileService : BaseTileService() { override fun getTileSubtitle(): String = RefreshRateUtils.getDisplaySubtitle(this) - override fun hasFeaturePermission(): Boolean = ShizukuUtils.hasPermission() + override fun hasFeaturePermission(): Boolean = ShellUtils.hasPermission(this) override fun getTileIcon(): Icon { return Icon.createWithResource(this, R.drawable.rounded_shutter_speed_24) diff --git a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt index 88f198a2a..4a1b2986e 100644 --- a/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt +++ b/app/src/main/java/com/sameerasw/essentials/services/tiles/ScreenOffAccessibilityService.kt @@ -15,6 +15,8 @@ import android.os.Looper import android.os.Vibrator import android.view.KeyEvent import android.view.accessibility.AccessibilityEvent +import android.media.session.MediaSessionManager +import com.sameerasw.essentials.services.NotificationListener import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.domain.HapticFeedbackType import com.sameerasw.essentials.services.InputEventListenerService @@ -280,6 +282,19 @@ class ScreenOffAccessibilityService : AccessibilityService(), SensorEventListene if (com.sameerasw.essentials.utils.AppUtil.isAndroidAutoRunning(this)) { return } + + val mediaSessionManager = getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager + val componentName = android.content.ComponentName(this, NotificationListener::class.java) + val sessions = try { + mediaSessionManager.getActiveSessions(componentName) + } catch (e: Exception) { + emptyList() + } + val isPlaying = sessions.any { it.playbackState?.state == android.media.session.PlaybackState.STATE_PLAYING } + if (!isPlaying) { + return + } + val title = prefs.getString("current_media_title", null) val artist = prefs.getString("current_media_artist", null) diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/HelpAndGuides.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/HelpAndGuides.kt index 93c3b8c1c..8e6d59aa9 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/HelpAndGuides.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/HelpAndGuides.kt @@ -82,8 +82,7 @@ fun HelpAndGuidesContent() { iconRes = R.drawable.rounded_adb_24, description = stringResource(R.string.instruction_section_shizuku_desc), links = listOf( - stringResource(R.string.label_shizuku_ritaka) to "https://github.com/RikkaApps/Shizuku", - stringResource(R.string.label_shizuku_tuozi) to "https://github.com/yangFenTuoZi/Shizuku" + stringResource(R.string.perm_shizuku_title) to "https://github.com/thedjchi/Shizuku" ) ), InstructionSection( diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/animations/LottieFeatureAnimation.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/animations/LottieFeatureAnimation.kt index 9ca2a1890..cb4bb7507 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/animations/LottieFeatureAnimation.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/animations/LottieFeatureAnimation.kt @@ -1,6 +1,8 @@ package com.sameerasw.essentials.ui.components.animations import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -34,17 +36,20 @@ fun LottieFeatureAnimation( ) val primaryColor = MaterialTheme.colorScheme.primary + val aspect = composition?.let { it.bounds.width().toFloat() / it.bounds.height().toFloat() } ?: 1f Box( modifier = modifier .fillMaxWidth() - .height(height) + .height(height), + contentAlignment = androidx.compose.ui.Alignment.Center ) { LottieAnimation( composition = composition, progress = { progress }, modifier = Modifier - .fillMaxSize() + .fillMaxHeight() + .aspectRatio(aspect) .padding(vertical = 16.dp) .graphicsLayer( compositingStrategy = CompositingStrategy.Offscreen, diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/LanguagePicker.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/LanguagePicker.kt index 7b34c4b0c..f3d8953f9 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/LanguagePicker.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/pickers/LanguagePicker.kt @@ -1,112 +1,41 @@ package com.sameerasw.essentials.ui.components.pickers -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.widthIn -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi -import androidx.compose.material3.ExposedDropdownMenuBox -import androidx.compose.material3.ExposedDropdownMenuDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.ListItem -import androidx.compose.material3.ListItemDefaults -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.ExposedDropdownMenuAnchorType -import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp import com.sameerasw.essentials.R -import com.sameerasw.essentials.utils.HapticUtil +import com.sameerasw.essentials.ui.components.cards.ConfigPickerItem +import com.sameerasw.essentials.ui.components.menus.SegmentedDropdownMenuItem import com.sameerasw.essentials.utils.LanguageUtils -@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun LanguagePicker( selectedLanguageCode: String, onLanguageSelected: (String) -> Unit, modifier: Modifier = Modifier ) { - val view = LocalView.current - var expanded by remember { mutableStateOf(false) } val languages = LanguageUtils.languages val selectedLanguage = languages.find { it.code == selectedLanguageCode } ?: languages.first() - ListItem( - onClick = {}, - modifier = modifier.fillMaxWidth(), - leadingContent = { - Icon( - painter = painterResource(id = R.drawable.rounded_globe_24), - contentDescription = null, - modifier = Modifier.size(24.dp), - tint = MaterialTheme.colorScheme.primary - ) - }, - contentPadding = PaddingValues( - horizontal = 16.dp, - vertical = 16.dp - ), - verticalAlignment = Alignment.CenterVertically, - colors = ListItemDefaults.colors( - containerColor = MaterialTheme.colorScheme.surfaceBright - ), - trailingContent = { - ExposedDropdownMenuBox( - expanded = expanded, - onExpandedChange = { expanded = !expanded }, - ) { - OutlinedTextField( - value = "${selectedLanguage.nativeName} (${selectedLanguage.name})", - onValueChange = {}, - readOnly = true, - modifier = Modifier - .widthIn(max = 200.dp) // Limit width to prevent overflow - .menuAnchor(ExposedDropdownMenuAnchorType.PrimaryEditable, true), - trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, - colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), - shape = RoundedCornerShape(12.dp), - textStyle = MaterialTheme.typography.bodySmall - ) - - ExposedDropdownMenu( - expanded = expanded, - onDismissRequest = { expanded = false } - ) { - languages.forEach { language -> - DropdownMenuItem( - text = { - Text(text = "${language.nativeName} (${language.name})") - }, - onClick = { - HapticUtil.performVirtualKeyHaptic(view) - onLanguageSelected(language.code) - expanded = false - }, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding - ) - } + ConfigPickerItem( + title = stringResource(R.string.label_app_language), + selectedValue = "${selectedLanguage.nativeName} (${selectedLanguage.name})", + iconRes = R.drawable.rounded_globe_24, + modifier = modifier.fillMaxWidth() + ) { + languages.forEach { language -> + SegmentedDropdownMenuItem( + text = { + Text(text = "${language.nativeName} (${language.name})") + }, + onClick = { + onLanguageSelected(language.code) } - } - }, - content = { - Text( - text = stringResource(R.string.label_app_language), - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurface ) } - ) + } } diff --git a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/AddRepoBottomSheet.kt b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/AddRepoBottomSheet.kt index ced2c4211..fbcdb853d 100644 --- a/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/AddRepoBottomSheet.kt +++ b/app/src/main/java/com/sameerasw/essentials/ui/components/sheets/AddRepoBottomSheet.kt @@ -80,8 +80,7 @@ fun AddRepoBottomSheet( var showAppPicker by remember { mutableStateOf(false) } // APK selection state - // APK selection state - var selectedApkName by remember { mutableStateOf("Auto") } + val selectedApkName by viewModel.selectedApkName // Search haptic feedback LaunchedEffect(isSearching) { @@ -397,7 +396,7 @@ fun AddRepoBottomSheet( Surface( onClick = { HapticUtil.performUIHaptic(view) - selectedApkName = option + viewModel.setSelectedApkName(option) }, color = MaterialTheme.colorScheme.surfaceContainer ) { @@ -418,7 +417,7 @@ fun AddRepoBottomSheet( selected = (selectedApkName == option), onClick = { HapticUtil.performUIHaptic(view) - selectedApkName = option + viewModel.setSelectedApkName(option) } ) } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/BatteryRingDrawer.kt b/app/src/main/java/com/sameerasw/essentials/utils/BatteryRingDrawer.kt index 3558b1be6..e033000f6 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/BatteryRingDrawer.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/BatteryRingDrawer.kt @@ -63,17 +63,20 @@ object BatteryRingDrawer { val topGapDegrees = if (hasStatusIcon) 60f else 0f val capAngleDegrees = ((strokeWidth / 2f) / radius) * (180f / PI.toFloat()) + + if (clampedLevel >= 100 && !hasStatusIcon) { + canvas.drawArc(rect, -90f, 360f, false, paint) + return bitmap + } + + val segmentGapDegrees = 8f val startAngle = -90f + (topGapDegrees / 2) val totalAvailableSweep = 360f - topGapDegrees - val progressSweepRaw = (clampedLevel / 100f) * totalAvailableSweep - val segmentGapDegrees = 8f - val visualStart = startAngle - val visualEnd = if (clampedLevel >= 100) { - startAngle + totalAvailableSweep - } else { - (startAngle + progressSweepRaw - (segmentGapDegrees / 2)).coerceAtLeast(startAngle) - } + val topPadding = if (!hasStatusIcon) segmentGapDegrees / 2f else 0f + val visualStart = startAngle + topPadding + val progressSweepRaw = (clampedLevel / 100f) * totalAvailableSweep + val visualEnd = (startAngle + progressSweepRaw - (segmentGapDegrees / 2)).coerceAtLeast(visualStart) val visualSpan = visualEnd - visualStart if (visualSpan > (capAngleDegrees * 2)) { @@ -125,7 +128,9 @@ object BatteryRingDrawer { val visualStart = (startAngle + progressSweepRaw + (segmentGapDegrees / 2)) .coerceAtMost(startAngle + totalAvailableSweep) - val visualEnd = startAngle + totalAvailableSweep + + val topPadding = if (!hasStatusIcon) segmentGapDegrees / 2f else 0f + val visualEnd = startAngle + totalAvailableSweep - topPadding val visualSpan = visualEnd - visualStart if (visualSpan > (trackCapAngleDegrees * 2)) { diff --git a/app/src/main/java/com/sameerasw/essentials/utils/LanguageUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/LanguageUtils.kt index 8faaeaa62..736dc6984 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/LanguageUtils.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/LanguageUtils.kt @@ -11,7 +11,8 @@ object LanguageUtils { Language("it", "Italian", "Italiano"), Language("ja", "Japanese", "日本語"), Language("ko", "Korean", "한국어"), - Language("pt", "Portuguese", "Português"), + Language("pt-BR", "Portuguese BR", "Português BR"), + Language("pt-PT", "Portuguese PT", "Português PT"), Language("ru", "Russian", "Русский"), Language("tr", "Turkish", "Türkçe"), Language("zh", "Chinese", "中文"), diff --git a/app/src/main/java/com/sameerasw/essentials/utils/RefreshRateUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/RefreshRateUtils.kt index 2e364bd99..c6979b3c9 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/RefreshRateUtils.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/RefreshRateUtils.kt @@ -67,36 +67,40 @@ object RefreshRateUtils { } } - fun applyFixedRefreshRate(value: Float): Boolean { - if (!ShizukuUtils.hasPermission()) return false + fun hasPermission(context: Context): Boolean { + return ShellUtils.hasPermission(context) + } + + fun applyFixedRefreshRate(context: Context, value: Float): Boolean { + if (!ShellUtils.hasPermission(context)) return false val clamped = normalizeRate(value) val formatted = formatRate(clamped) - ShizukuUtils.runCommand("settings put system $KEY_PEAK_REFRESH_RATE $formatted") - ShizukuUtils.runCommand("settings put system $KEY_MIN_REFRESH_RATE $formatted") + ShellUtils.runCommand(context, "settings put system $KEY_PEAK_REFRESH_RATE $formatted") + ShellUtils.runCommand(context, "settings put system $KEY_MIN_REFRESH_RATE $formatted") return true } - fun applyRangeRefreshRate(minValue: Float, peakValue: Float): Boolean { - if (!ShizukuUtils.hasPermission()) return false + fun applyRangeRefreshRate(context: Context, minValue: Float, peakValue: Float): Boolean { + if (!ShellUtils.hasPermission(context)) return false val safeMin = normalizeRate(minValue) val safePeak = normalizeRate(maxOf(minValue, peakValue)) - ShizukuUtils.runCommand("settings put system $KEY_MIN_REFRESH_RATE ${formatRate(safeMin)}") - ShizukuUtils.runCommand("settings put system $KEY_PEAK_REFRESH_RATE ${formatRate(safePeak)}") + ShellUtils.runCommand(context, "settings put system $KEY_MIN_REFRESH_RATE ${formatRate(safeMin)}") + ShellUtils.runCommand(context, "settings put system $KEY_PEAK_REFRESH_RATE ${formatRate(safePeak)}") return true } - fun resetRefreshRate(restoreInfinityPeak: Boolean = false): Boolean { - if (!ShizukuUtils.hasPermission()) return false + fun resetRefreshRate(context: Context, restoreInfinityPeak: Boolean = false): Boolean { + if (!ShellUtils.hasPermission(context)) return false // Clear both namespaces first, then restore the original system-managed peak behavior. - ShizukuUtils.runCommand("settings delete system $KEY_MIN_REFRESH_RATE") - ShizukuUtils.runCommand("settings delete system $KEY_PEAK_REFRESH_RATE") - ShizukuUtils.runCommand("settings delete global $KEY_PEAK_REFRESH_RATE") - ShizukuUtils.runCommand("settings delete global $KEY_MIN_REFRESH_RATE") + ShellUtils.runCommand(context, "settings delete system $KEY_MIN_REFRESH_RATE") + ShellUtils.runCommand(context, "settings delete system $KEY_PEAK_REFRESH_RATE") + ShellUtils.runCommand(context, "settings delete global $KEY_PEAK_REFRESH_RATE") + ShellUtils.runCommand(context, "settings delete global $KEY_MIN_REFRESH_RATE") if (restoreInfinityPeak) { - ShizukuUtils.runCommand("settings put system $KEY_PEAK_REFRESH_RATE Infinity") + ShellUtils.runCommand(context, "settings put system $KEY_PEAK_REFRESH_RATE Infinity") } return true } diff --git a/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt b/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt index e1dff2ef0..0986b5f7e 100644 --- a/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt +++ b/app/src/main/java/com/sameerasw/essentials/utils/ShizukuUtils.kt @@ -12,9 +12,6 @@ object ShizukuUtils { private val binderReceivedListener = Shizuku.OnBinderReceivedListener { binder = Shizuku.getBinder() - if (Shizuku.checkSelfPermission() != PackageManager.PERMISSION_GRANTED) { - Shizuku.requestPermission(1003) - } } private val binderDeadListener = Shizuku.OnBinderDeadListener { @@ -22,7 +19,19 @@ object ShizukuUtils { } private val isBinderAlive: Boolean - get() = binder?.isBinderAlive == true + get() { + if (binder?.isBinderAlive == true) return true + return try { + if (Shizuku.pingBinder()) { + binder = Shizuku.getBinder() + binder?.isBinderAlive == true + } else { + false + } + } catch (@Suppress("UNUSED_PARAMETER") e: Exception) { + false + } + } fun initialize() { Shizuku.addBinderReceivedListener(binderReceivedListener) diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt index 3a0d9bba6..04863e69e 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/AppUpdatesViewModel.kt @@ -13,6 +13,8 @@ import com.sameerasw.essentials.data.repository.GitHubRepository import com.sameerasw.essentials.data.repository.SettingsRepository import com.sameerasw.essentials.domain.model.NotificationApp import com.sameerasw.essentials.domain.model.TrackedRepo +import com.sameerasw.essentials.domain.model.github.GitHubAsset +import com.sameerasw.essentials.domain.model.github.GitHubOwner import com.sameerasw.essentials.domain.model.github.GitHubRelease import com.sameerasw.essentials.domain.model.github.GitHubRepo import com.sameerasw.essentials.utils.AppUtil @@ -73,6 +75,13 @@ class AppUpdatesViewModel : ViewModel() { private val _notificationsEnabled = mutableStateOf(true) val notificationsEnabled: State = _notificationsEnabled + private val _selectedApkName = mutableStateOf("Auto") + val selectedApkName: State = _selectedApkName + + fun setSelectedApkName(name: String) { + _selectedApkName.value = name + } + private val _installingRepoId = mutableStateOf(null) val installingRepoId: State = _installingRepoId @@ -203,39 +212,73 @@ class AppUpdatesViewModel : ViewModel() { fun prepareEdit(context: Context, repo: TrackedRepo) { _searchQuery.value = repo.fullName - _isSearching.value = true + _isSearching.value = false _errorMessage.value = null - _searchResult.value = null - _latestRelease.value = null + + // Build local fallback repo info from cache so it displays instantly + val localOwner = GitHubOwner( + login = repo.owner, + avatarUrl = repo.avatarUrl + ) + _searchResult.value = GitHubRepo( + id = 0L, + name = repo.name, + fullName = repo.fullName, + description = repo.description, + stars = repo.stars, + owner = localOwner + ) + + // Build local fallback release info from cache + val localAssets = if (repo.downloadUrl != null) { + listOf( + GitHubAsset( + name = repo.selectedApkName.takeIf { it != "Auto" } ?: repo.downloadUrl.substringAfterLast("/"), + downloadUrl = repo.downloadUrl + ) + ) + } else { + emptyList() + } + + _latestRelease.value = GitHubRelease( + tagName = repo.latestTagName, + name = repo.latestReleaseName, + body = repo.latestReleaseBody, + publishedAt = repo.publishedAt, + htmlUrl = repo.latestReleaseUrl ?: "", + prerelease = repo.allowPreReleases, + assets = localAssets + ) + _readmeContent.value = null - _selectedApp.value = null _allowPreReleases.value = repo.allowPreReleases _notificationsEnabled.value = repo.notificationsEnabled + _selectedApkName.value = repo.selectedApkName + _selectedApp.value = null viewModelScope.launch { + if (repo.mappedPackageName != null) { + val installedApps = AppUtil.getAppsByPackageNames(context, listOf(repo.mappedPackageName)) + _selectedApp.value = installedApps.firstOrNull() + } + try { val token = SettingsRepository(context).getGitHubToken() val repoInfo = gitHubRepository.getRepoInfo(repo.owner, repo.name, token) val release = gitHubRepository.getLatestRelease(repo.owner, repo.name, token) - _searchResult.value = repoInfo - _latestRelease.value = release - _readmeContent.value = gitHubRepository.getReadme(repo.owner, repo.name, token) - - // Set mapped app - if (repo.mappedPackageName != null) { - val installedApps = AppUtil.getInstalledApps(context) - _selectedApp.value = - installedApps.find { it.packageName == repo.mappedPackageName } + + if (repoInfo != null) { + _searchResult.value = repoInfo } + if (release != null) { + _latestRelease.value = release + } + _readmeContent.value = gitHubRepository.getReadme(repo.owner, repo.name, token) } catch (e: Exception) { if (e.message == "RATE_LIMIT") { - _errorMessage.value = - context.getString(R.string.error_rate_limited) + _errorMessage.value = context.getString(R.string.error_rate_limited) } - // Fallback to offline data? User said it should open but didn't specify offline support. - // For now just clear searching - } finally { - _isSearching.value = false } } } @@ -282,6 +325,7 @@ class AppUpdatesViewModel : ViewModel() { _readmeContent.value = null _allowPreReleases.value = false _notificationsEnabled.value = true + _selectedApkName.value = "Auto" } fun clearError() { diff --git a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt index 24179b8d8..34f0d1c47 100644 --- a/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt +++ b/app/src/main/java/com/sameerasw/essentials/viewmodels/MainViewModel.kt @@ -740,7 +740,14 @@ class MainViewModel : ViewModel() { // Sync with system per-app language settings val currentLocales = AppCompatDelegate.getApplicationLocales() if (!currentLocales.isEmpty) { - appLanguage.value = currentLocales.get(0)?.language ?: "en" + val locale = currentLocales.get(0) + val langTag = locale?.toLanguageTag() ?: "en" + appLanguage.value = when { + langTag.startsWith("pt-BR") -> "pt-BR" + langTag.startsWith("pt-PT") -> "pt-PT" + langTag.startsWith("pt") -> "pt-BR" // Fallback to Brazilian Portuguese as primary translated option + else -> locale?.language ?: "en" + } } else { appLanguage.value = "en" } @@ -2140,7 +2147,7 @@ class MainViewModel : ViewModel() { return } - if (RefreshRateUtils.applyFixedRefreshRate(value)) { + if (RefreshRateUtils.applyFixedRefreshRate(context, value)) { val normalized = RefreshRateUtils.normalizeRate(value) fixedRefreshRate.floatValue = normalized minRefreshRate.floatValue = normalized @@ -2170,7 +2177,7 @@ class MainViewModel : ViewModel() { return } - if (RefreshRateUtils.applyRangeRefreshRate(minValue, peakValue)) { + if (RefreshRateUtils.applyRangeRefreshRate(context, minValue, peakValue)) { val normalizedMin = RefreshRateUtils.normalizeRate(minValue) val normalizedPeak = RefreshRateUtils.normalizeRate(maxOf(minValue, peakValue)) minRefreshRate.floatValue = normalizedMin @@ -2190,7 +2197,7 @@ class MainViewModel : ViewModel() { fun resetRefreshRate(context: Context) { val restoreInfinityPeak = settingsRepository.shouldRestoreInfinityPeakOnRefreshRateReset() - if (RefreshRateUtils.resetRefreshRate(restoreInfinityPeak)) { + if (RefreshRateUtils.resetRefreshRate(context, restoreInfinityPeak)) { fixedRefreshRate.floatValue = 0f minRefreshRate.floatValue = 0f peakRefreshRate.floatValue = 0f diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000..fa3aba141 --- /dev/null +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,1457 @@ + + + Customization + BETA + Attempt Shizuku restart + Shizuku Restart Warning + Since wireless debugging was toggled, Shizuku services might not have started automatically causing freezing to not function. Consider turning on attempt shizuku restart as well + Essentials Accessibility Service\n\nThis service is required for the following advanced features:\n\n• Physical Button Remapping:\nDetects volume button presses even when the screen is off to trigger actions like the Flashlight.\n\n• Per-App Settings:\nMonitors the currently active app to apply specific profiles for Dynamic Night Light, Notification Lighting Colors, and App Lock.\n\n• Screen Control:\nAllows the app to lock the screen (e.g. via Double Tap or Widgets) and detect screen state changes.\n\n• Security:\nPrevents unauthorized changes by detecting window content when the device is locked.\n\nNo input text or sensitive user data is collected or transmitted. + Congelamento de aplicativos + Desative aplicativos que raramente são usados + Frozen Apps + Abra o congelamento de aplicativos + Aplicativo congelado + Tela vazia fora do widget + Congelamento de aplicativos + Pulso de lanterna + Confira os pré-lançamentos + Pode ser instável + Default tab + + Segurança + Ativar bloqueio de aplicativo + Segurança de bloqueio de aplicativos + Autenticar para ativar o bloqueio de aplicativos + Autenticar para desativar o bloqueio de aplicativos + Selecione aplicativos bloqueados + Escolha quais aplicativos exigem autenticação + Proteja seus aplicativos com autenticação biométrica. Os aplicativos bloqueados exigirão autenticação ao serem iniciados. Permanecem desbloqueados até que a tela seja desligada. + Esteja ciente de que esta não é uma solução robusta, pois é apenas um aplicativo de terceiros. Se você precisar de segurança forte, considere usar o Private Space ou outros recursos semelhantes. + Outra observação: o prompt de autenticação biométrica só permite usar métodos de classe seguros FORTES. Os métodos de segurança de desbloqueio facial na classe FRACA em dispositivos como o Pixel 7 só poderão utilizar os outros métodos de autenticação STRONG disponíveis, como impressão digital ou PIN. + Use usage access + Instead of accessibility (Freeze, App Lock, Dynamic Night Light) + + Ativar remapeamento de botão + Use Shizuku or Root or Root + Funciona com a tela desligada (recomendado) + Shizuku não está correndo + Detectado %1$s + Status: %1$s + Abra Shizuku + Lanterna + Opções de lanterna + Ajustar o desbotamento e outras configurações + Tema preto como breu + Use fundo preto puro no modo escuro + Feedback tátil + Remapear toque longo + Tela desligada + Tela ativada + Aumentar o volume + Diminuir volume + Alternar lanterna + Reprodução/pausa de mídia + Próxima mídia + Mídia anterior + Alternar vibração + Alternar mudo + Assistente de IA + Faça uma captura de tela + Ciclo de modos de som + Circle to Search + Curtir a música atual + Como configurações de música + Este recurso requer acesso à notificação para detectar a mídia atualmente sendo reproduzida e acionar a ação semelhante. Ative-o abaixo. + Mostrar mensagem de brinde + Mostrar sobreposição no AOD + Essentials On Display + Glance at media and info on AOD + Docked mode + Keep the overlay visible indefinitely while music is playing on AOD + Respect new notifications + Briefly disable for new notifications + Random shapes + Use various Material 3 shapes for the album art + Album art + Clock + Always use while charging + Force Fill mode when the device is plugged in regardless of the selected mode + Size + Visão geral da notificação + Mantenha o AOD ativado enquanto as notificações estiverem pendentes + Os mesmos aplicativos da iluminação de notificação + Este recurso ativará dinamicamente o Always on Display quando uma notificação chegar de um aplicativo selecionado e o desativará quando todas as notificações correspondentes forem descartadas. Escolha aplicativos ou use a mesma seleção da iluminação de notificação. + Conceder acesso à notificação + Alternar volume de mídia + Quando a tela estiver desligada, mantenha pressionado o botão selecionado para acionar a ação atribuída. Em dispositivos Pixel, esta ação só é acionada se o AOD estiver ativado devido a limitações do sistema. + Quando a tela estiver ligada, mantenha pressionado o botão selecionado para acionar a ação atribuída. + Intensidade da lanterna + Aparecer e desaparecer gradualmente + Alternar suavemente a lanterna + Controles globais + Fade-in global da lanterna + Ajustar intensidade + Volume + - ajusta a intensidade da lanterna + Atualização ao vivo + Mostrar brilho na barra de status + Outro + Sempre desligue a lanterna + Mesmo quando a tela está ligada + Configurações + + Mostrar notificação + Postar notificações + Permite que o aplicativo mostre notificações + Conceder permissão + Cafeína Ativa + Ativo + A tela está sendo mantida ativa + Ignorar a otimização da bateria + Abortar com tela desligada + Pular contagem regressiva + Comece com cafeína imediatamente. + Predefinições de tempo limite + Selecione as durações disponíveis para o bloco QS + 5m + 10m + 30m + Acesso Não perturbe + Necessário para alternar entre os modos de som, vibração e mudo + 1h + + Começando em %1$ds… + %1$s restante + Notificação persistente para cafeína + + Ativar luz noturna dinâmica + Aplicativos que desligam a luz noturna + Selecione aplicativos + + Controle de aplicativos + Congelar + Descongelar + Remove + Create shortcut + App info + What is Freeze? + App freezing disables the app\'s launch activity which removes it from the app list and updates. It will prevent the app from starting at all until it\'s unfrozen which saves resources but you will need to unfreeze from here or manually re-enable. + DO NOT FREEZE COMMUNICATION APPS + What is Suspend? + Suspending an app used to pause the app activity and prevent background executions but with recent Android changes, it only pauses the notifications from appearing and that\'s pretty much it. But does allows you to unpause from the launcher app list as they will be still available as grayscale paused app icons. + Should work the same as the native app pause/ focus mode features. + Mais opções + Congelar todos os aplicativos + Descongelar todos os aplicativos + Exportar lista de aplicativos congelados + Importar lista de aplicativos congelados + Escolha aplicativos para congelar + Escolha quais aplicativos podem ser congelados + Show in launcher + Add a launcher shortcut for easier access + Automação + Congelar quando bloqueado + Atraso de congelamento + Imediato + 1m + 5m + 15m + Manual + Congelar aplicativos automaticamente + Congele aplicativos selecionados quando o dispositivo for bloqueado. Escolha um atraso para evitar o congelamento de aplicativos se você desbloquear a tela logo após desligá-la. + O congelamento de aplicativos do sistema pode ser perigoso e causar comportamento inesperado. + Ativar nas configurações + Não congele aplicativos ativos + Estatísticas de uso + Necessário para detectar quais aplicativos estão atualmente em primeiro plano para evitar congelá-los + Required to detect foreground apps for App Lock features when accessibility is not used. + Necessário para detectar mídia em reprodução e notificações ativas para evitar congelá-las + Freeze mode + Freezing + App suspension + Can not switch mode while apps are frozen. Please unfreeze all and try again. + + Mostrar apenas quando a tela está desligada + Ignorar notificações silenciosas + Ignorar notificações persistentes + Pulso de lanterna + Pulso de lanterna + Somente enquanto estiver voltado para baixo + Os mesmos aplicativos da iluminação de notificação + Max brightness + Preview pulse + Estilo + Ajuste de curso + Raio de canto + Espessura do traço + Ajuste de brilho + Propagação de brilho + Colocação + Posição horizontal + Posição vertical + Ajuste do indicador + Escala + Duração + Sweep + Position + Random shapes + Stroke thickness + Left + Center + Right + Animação + Contagem de pulso + Duração do pulso + Modo de cor + Exibição ambiente + Exibição ambiente + Adequado se você não estiver usando AOD. + Acorde a tela e mostre a iluminação + Mostrar tela de bloqueio + Sem sobreposição preta + + Adicionar + Added + Já adicionado + Requer Android 13+ + Desfoque da interface do usuário + Bolhas + Conteúdo Sensível + Toque para acordar + AOD + Cafeína + Modo de som + Iluminação de notificação + Luz noturna dinâmica + Segurança bloqueada + Bloqueio de aplicativo + Áudio Mono + Lanterna + Congelamento de aplicativos + Pulso de lanterna + Fique acordado + Teclado Essencial + Inglês (EUA) + Ativo + Inativo + Opções do desenvolvedor + Alterne facilmente as opções do desenvolvedor do sistema em um bloco QS. Isso pode redefinir algumas das configurações do desenvolvedor que você modificou. + Refresh Rate + Cycle between the system default and preset refresh rates directly from a Quick Settings tile using Shizuku. + NFC + DNS privado + Automático + Desligado + Depuração USB + Seletor de cores + Are you sure you\'re on Androdi 17? (╯°_°)╯ + Conta-gotas + Sobre + Desligado + DNS privado personalizado + Predefinições de DNS comuns + Add DNS Preset + Preset name + Reset + Delete preset + Are you sure you want to reset all DNS presets to defaults? This will remove all your custom presets. + Nome do host do provedor + DNS do AdGuard + dns.adguard.com + DNS público do Google + dns.google + DNS da Cloudflare + 1dot1dot1dot1.cloudflare-dns.com + DNS Quad9 + dns.quad9.net + Navegação limpa + adult-filter-dns.cleanbrowsing.org + Carregando + Limite a 80% + Adaptativo + Não otimizado + Permissão ausente + + Screen locked security + Screen Locked Security + ⚠️ AVISO + Authenticate to enable screen locked security + Authenticate to disable screen locked security + Disable quick setting tiles when the device is locked + Disable QS Locked + Prevent expanding Quick Settings when device is locked + + Reordenar modos + Pressione e segure para alternar + Arraste para reordenar + Som + Vibrar + Silencioso + + Conectividade + Telefone e rede + Áudio e mídia + Status do sistema + Específico do OEM + + WiFi + Bluetooth + NFC / Félica + VPN + Modo Avião + Ponto de acesso + Elenco + Dados móveis + Sinal de telefone + VoLTE/VoNR + Chamadas WiFi / VoWiFi + Status/sincronização da chamada + TTY + Volume + Fone de ouvido + Viva-voz + DMB + Relógio + Método de entrada (IME) + Alarme + Bateria + Economia de energia + Economia de dados + Bloqueio de rotação + Localização/GPS + Sincronizar + Perfil gerenciado + Não incomodar + Privacidade e pasta segura + Status de segurança (SU) + Rato/Teclado OTG + Recursos inteligentes da Samsung + Serviços Samsung + Ethernet + + Mostrar segundos no relógio + Porcentagem de bateria + Sempre + Carregando + Nunca + Câmera e microfone usam chips + Dados inteligentes + Ler estado do telefone + Necessário para detectar o tipo de rede para o recurso Smart Data + Necessário para detectar alterações no status da chamada para acionar o feedback tátil. + Visibilidade Inteligente + Wi-Fi inteligente + Ocultar dados móveis quando o WiFi estiver conectado + Ocultar dados móveis em determinados modos + Redefinir todos os ícones + Mais configurações + Advanced (Requires Shizuku/Root) + Hide all system icons + Clear all non-notification icons from the status bar + Only hide system icons when locked + Hides the status bar system icons when the device is locked and restores them upon unlock + Hide clock + Completely remove the clock from the status bar + Hide notifications + Hide all incoming notification icons + Hide gesture bar + In gesture navigation + Show on launcher + Dynamically show the gesture bar only when on the home screen + Circle to Search gesture + Long-press the bottom area to trigger Circle to Search + Gesture zone height + Adjust the height of the touch area at the bottom + Other customizations + Additional system tweaks and modifications + Observe que a implementação dessas opções pode depender do OEM e algumas podem não funcionar. + + Outro + + Segundos do relógio + Mostrar segundos no relógio da barra de status + Porcentagem de bateria + Configurar a visibilidade da porcentagem da bateria + Chips de privacidade + Mostrar indicador quando a câmera ou o microfone estão em uso + Alternar visibilidade para %1$s + Fixar nos favoritos + Liberar dos Favoritos + + + Ferramentas + Visuais + Sistema + + Search for Tools, Mods and Tweaks + Nenhum resultado para \"%1$s\" + Resultados da pesquisa + %1$s requer as seguintes permissões + + Widget de tela desligada + Widget invisível para desligar a tela + Ícones da barra de status + Controlar a visibilidade dos ícones da barra de status + Cafeinar + Mantenha a tela ativa + Modo de economia de energia do Maps + Para qualquer dispositivo Android + Iluminação de notificação + Acenda para notificações + Pulse a lanterna para notificações + Bloco de modo de som + Vibrações de chamada + Vibrar para ações de chamada + Mostrar dispositivos Bluetooth + Exibir o nível da bateria dos dispositivos Bluetooth conectados + Limitar o máximo de dispositivos + Ajustar o máximo de dispositivos visíveis no widget + Plano de fundo do widget + Mostrar plano de fundo do widget + + Automação de gatilho + Agende uma ação para ser acionada em uma observação + Automação de Estado + Agende uma ação para execução com base no estado de uma condição de entrada e saída + Nova Automação + Editar automação + Ações de link + Lidar com links com vários aplicativos + Suspender notificações do sistema + Adiar notificações persistentes + Blocos de configurações rápidas + Ver tudo + Remapeamento de botão + Remapear ações de botão de hardware + Luz noturna dinâmica + Alternar luz noturna com base no aplicativo + Segurança bloqueada com tela + Bloqueio de aplicativo + Aplicativos seguros com biometria + Auto lock delay + After leaving the app + None + 1 minute + 5 minutes + 10 minutes + 20 minutes + 30 minutes + Congelar + Desative aplicativos raramente usados + Marca d\'água + Adicione dados EXIF ​​e logotipos às fotos + Sempre em exibição + Mostrar hora e informações enquanto a tela está desligada + Sincronização de calendário + Sync your phone calendar to your watch + Lock from Watch + Lock your phone remotely + Lock Mode + Screen off + Lock + Using device admin lock will disable biometrics + Connected + Unknown Watch + Sobreposição + Quadro + Marca do dispositivo + Dados EXIF + Escolha a imagem + Imagem salva na galeria + Compartilhar + Configurações EXIF + Distância focal + Abertura + ISO + Velocidade do obturador + Data e hora + Mover para o topo + Alinhar à esquerda + Tamanho da marca + Tamanho dos dados + Tamanho do texto + Tamanho da fonte + Texto personalizado + Digite seu texto... + Espaçamento + Largura da borda + Cantos Arredondados + Cor + Logotipo + Mostrar logotipo + Tamanho do logotipo + Editar textos de marca d\'água + Marca do dispositivo + Data e hora + Sem informações de data + Girar para a esquerda + Girar para a direita + Próximo + OK + Salvar alterações + Configurações de sincronização de calendário + Sincronize calendários específicos + Sincronização Periódica + Sincronize a cada 15 minutos se forem encontradas alterações + Sincronizar agora + Acione a sincronização imediata para assistir + Nenhuma agenda local encontrada + A sincronização do calendário foi iniciada + + Feedback tátil do widget + Escolha feedback tátil para toques em widgets + Wi-Fi inteligente + Ocultar dados móveis quando o WiFi estiver conectado + Dados inteligentes + Ocultar dados móveis em determinados modos + Redefinir todos os ícones + Redefinir a visibilidade do ícone da barra de status para o padrão + Abortar cafeína com a tela desligada + Desligue automaticamente o Caffeinate ao bloquear manualmente o dispositivo + Estilo de iluminação + Escolha entre Stroke, Glow, Spinner e muito mais + Raio de canto + Ajuste o raio do canto da iluminação de notificação + Ignorar notificações silenciosas + Não mostre iluminação para notificações silenciosas + Pulso de lanterna + Lanterna pulsa lentamente para novas notificações + Somente enquanto estiver voltado para baixo + Lanterna de pulso somente quando o dispositivo está voltado para baixo + Nenhum canal do sistema descoberto ainda. Eles aparecerão aqui assim que forem detectados. + Desfoque da interface do usuário + Alternar desfoque da IU em todo o sistema + Bolhas + Ativar bolhas flutuantes nas janelas + Conteúdo Sensível + Ocultar detalhes da notificação na tela de bloqueio + Toque para acordar + Toque duas vezes para ativar o controle + AOD + Alternar sempre em exibição + Cafeína + Alternar manter a tela ativa + Modo de som + Ciclo de modos de som (Toque/Vibrar/Silencioso) + Iluminação de notificação + Alternar serviço de iluminação de notificação + Luz noturna dinâmica + Alternar automação de luz noturna + Segurança bloqueada + Segurança de rede na alternância da tela de bloqueio + Áudio Mono + Forçar alternância de saída de áudio mono + Lanterna + Alternar lanterna dedicada + Congelamento de aplicativos + Inicie a grade de congelamento do aplicativo + Pulso de lanterna + Alternar pulso da lanterna de notificação + Alternar opção de desenvolvedor para ficar acordado + DNS privado + Alternar entre os modos DNS privado (Desligado/Automático/Nome do host) + Depuração USB + Alternar opção de desenvolvedor de depuração USB + Refresh Rate + Cycle between the system default and preset screen refresh rates + Ativar remapeamento de botão + Alternância mestre para remapeamento do botão de volume + Remapear feedback tátil + Feedback de vibração quando o botão remapeado é pressionado + Alternar lanterna + Alternar lanterna com botões de volume + Ativar luz noturna dinâmica + Interruptor mestre para luz noturna dinâmica + Ativar bloqueio de aplicativo + Alternância mestre para bloqueio de aplicativos + Selecione aplicativos bloqueados + Escolha quais aplicativos exigem autenticação + Escolha aplicativos para congelar + Escolha quais aplicativos podem ser congelados + Congelar todos os aplicativos + Congele imediatamente todos os aplicativos escolhidos + Congelar quando bloqueado + Congelar aplicativos selecionados quando o dispositivo for bloqueado + Atraso de congelamento + Atraso antes de congelar após o bloqueio + + Shizuku + Necessário para comandos avançados. Instale o Shizuku da Play Store. + Instale Shizuku + Conceder permissão + Necessário para executar comandos de economia de energia enquanto os mapas estão navegando. + Requer Shizuku ou Root + Acesso à raiz + Permissões necessárias para ações do sistema usando privilégios Root. + Ouvinte de notificação + Requer acesso de ouvinte de notificação para monitorar o status de navegação do Google Maps e ativar a economia de energia quando não estiver navegando. + Requer acesso de ouvinte de notificação para detectar novas notificações e acionar iluminação de borda. + Requer acesso de ouvinte de notificação para monitorar e adiar notificações indesejadas do sistema. + Serviço de acessibilidade + Necessário para App Lock, widget Screen off e outros recursos para detectar interações + Necessário para acionar a iluminação de notificação em novas notificações + Navegador padrão + Necessário para lidar com links de forma eficiente + Necessário para interceptar eventos de botão de hardware + Required to intercept volume key events while the screen is off to trigger the Essentials On Display overlay. + Necessário para monitorar aplicativos em primeiro plano. + Gravar configurações seguras + Obrigatório para ícones da barra de status e segurança de tela bloqueada + Necessário para alternar a luz noturna. Conceda via ADB ou root. + Modificar configurações do sistema + Necessário para alternar o brilho adaptável e outras configurações do sistema + Permissão de sobreposição + Necessário para exibir a sobreposição de iluminação de notificação na tela + Administrador do dispositivo + Necessário para bloquear o dispositivo (desativando a biometria) em tentativas de acesso não autorizado + Conceder permissão + Copiar ADB + Verificar + Ativar nas configurações + Como conceder + Granted + Otimização da bateria + Certifique-se de que o serviço não seja eliminado pelo sistema para economizar energia. + + Fundamentos + Congelar + Congelado + faça você mesmo + Aplicativos + Aplicativos desativados + Faça você mesmo + Encontre e gerencie aplicativos + Atualizações de aplicativos + Atualizações de aplicativos + Adicionar repositório + Editar repositório + Insira o URL do repositório GitHub ou proprietário/repo + Acompanhar + Nenhum APK encontrado na versão mais recente + Repositório não encontrado + Último lançamento + Ver LEIA-ME + %d Estrelas + Aplicativo instalado + Não instalado + Escolha o aplicativo + Selecione o aplicativo + Cancelar rastreamento + Pendente + Atualizado + Acompanhe e baixe os lançamentos mais recentes de seus aplicativos favoritos diretamente do GitHub. + Formato inválido. Use proprietário/repo ou URL do GitHub + Ocorreu um erro durante a pesquisa + Auto + Opções + Confira os pré-lançamentos + Notificações + Limite de taxa do GitHub excedido. Por favor, tente novamente mais tarde. + + Configuração do teclado + Ativar nas configurações + Mudar para o Essencial + Habilitado + Desabilitado + Brilho adaptativo + Economia de energia nos mapas + Procurar + Parar + Procurar + Search frozen apps + + Voltar + Voltar + Configurações + Reportar um bug + + Crash reporting + Off + Auto + O Essentials crashou, o relatório foi enviado + Simulate crash + Bem vindo ao Essentials + A Toolbox for Android Nerds + por sameerasw.com + Vamos começar + Acknowledgement + Este aplicativo é uma coleção de utilidades que podem interagir profundamente com o sistema do seu dispositivo. Usando algumas utilidades pode modificar seu sistema ou comportamento em formas inesperadas. \n\n Você só precisa conceder permissões necessárias, que são utilizadas por utilidades que você está usando, assim dando acesso completo ao comportamento do aplicativo. \n\nAlém disso, o aplicativo não rastreia dados nem os guarda, não preciso deles...(Você pode se referir ao código-fonte para mais informações). \n\nEste aplicativo Sempre vai ser gratuito e de código aberto. Nunca pague, ou instale o Essentials de fontes desconhecidas. + WARNING: Proceed with caution. The developer takes no responsibility for any system instability, data loss, or other issues caused by the use of this app. By proceeding, you acknowledge these risks. + I know you didn\'t even read this carefully but, in case you need any help, feel free to reach out the developer or the community. + I Understand + Qualquer hora que estiver sem ideia do que alguma utilidade ou algum Quick Settings Tile faz, pressione longamente e selecione \"O quê é isso?\" para aprender mais. + You can report bugs or find helpful guides anytime in the app settings. + Me deixe entrar + Preferences + Configure algumas coisas básicas para começar. + Configurações do aplicativo + Language + Haptic Feedback + Updates + Procurar por atualizações automaticamente + Procurar por atualização no início do aplicativo + All Set + Check What\'s New? + Welcome back to Essentials + See what\'s new + Default + Glove mode + Glove Mode + Quickly toggle between Default and Glove mode UI profiles + Increase touch sensitivity + Improve touch screen responsiveness when using gloves or screen protectors + Auto rotate + Automatically rotate the screen when the device is turned + Screen timeout + How long the screen stays on before turning off automatically + %d seconds + 1 minute + %d minutes + Restart SystemUI + Kills and restarts SystemUI process to apply some changes immediately + Couldn\'t load the release note + View on web + Feito + Visualização + Guia de ajuda + O que é isso? + Atualização disponível + Glance at your device\'s hardware and software specifications in detail. This information is fetched from GSMArena and system properties to provide a comprehensive overview of your Android device. + Essentials On Display shows a Now Playing overlay on your lock screen when music is playing and playback changes. \n\nIf your device does not support overlays over AOD, you can opt for the Ambience screensaver added in your Android settings as an alternative while charging. + A iluminação de notificação adiciona um belo efeito de iluminação de borda quando você recebe notificações.\n\nVocê pode personalizar o estilo, as cores e o comportamento da animação. Funciona mesmo quando a tela está desligada (depende do OEM) ou na parte superior do seu aplicativo atual. Escolha aplicativos, prioridade de notificação ou qual comportamento deve ser desencadeado a partir de determinados controles. Se o seu OEM não suportar sobreposições acima de AOD, use a opção de exibição ambiente encontrada abaixo. + Desligue facilmente a tela com um toque em um widget redimensionável transparente que não adiciona ícones ou qualquer confusão à sua tela inicial. + Assuma o controle total sobre os ícones da barra de status.\n\nOculte ícones específicos como WiFi, Bluetooth ou dados de celular para manter sua barra de status limpa. Você também pode personalizar o formato do relógio e o indicador de bateria com alguns controles inteligentes. Esta é a lista de controles AOSP disponíveis, portanto o sistema operacional do seu dispositivo pode não respeitar todos os controles. + A cafeína evita que sua tela desligue automaticamente.\n\nMantenha sua tela ativa por um período específico ou indefinidamente. Útil ao ler artigos longos ou fazer referência a uma receita. + Obtenha o modo de economia de energia do Google Maps exclusivo da série Pixel 10 com fundo preto mínimo para exibir na tela de bloqueio em qualquer dispositivo Android. Inicie uma sessão de navegação, desligue e ligue a tela novamente. + Pulse a lanterna ao receber uma notificação.\n\nCom os dispositivos com suporte de hardware para escurecimento da lanterna, o pulso será suavemente animado. + Adie notificações irritantes e persistentes do sistema que não podem ser modificadas por padrão. \n\nAguarde até que a notificação chegue e entre neste recurso onde o canal de notificação\'s será listado. Selecione para adiar na próxima vez.\n\nQualquer notificação adiada ainda pode ser acessada no seu histórico de notificações no Android. + Adicione blocos personalizados ao painel de configurações rápidas.\n\nPressione e segure qualquer um deles para saber o que eles fazem. + Remapeie seus botões de hardware para executar diferentes ações e atalhos.\n\nPersonalize o que acontece quando você pressiona longamente os botões de volume com certas condições. \n\nAlguns comportamentos, como o gatilho de desligamento da tela ou os controles da lanterna, podem depender do OEM de sua implementação e podem não funcionar em todos os dispositivos conforme o esperado. Alguns cenários podem ser contornados usando permissões Shizuku, mas podem não proporcionar a mesma experiência devido às implementações. + Alterna automaticamente o filtro de luz azul da tela com base no aplicativo em primeiro plano. + Aumente a segurança quando seu dispositivo estiver bloqueado.\n\nRestringir o acesso a alguns blocos QS sensíveis, evitando modificações não autorizadas na rede e evitando ainda mais tentativas de fazê-lo, aumentando a velocidade da animação para evitar spam de toque.\n\nEste recurso não é robusto e pode ter falhas, como alguns blocos que permitem alternar diretamente, como bluetooth ou modo de vôo, não podendo ser impedido. + Proteja seus aplicativos com uma camada de autenticação secundária.\n\nO método de autenticação da tela de bloqueio do seu dispositivo será usado desde que atenda ao nível de segurança biométrica classe 3 pelos padrões Android. + Seja notificado quando chegar mais perto do seu destino para garantir que você nunca perca a parada.\n\nVá para o Google Maps, mantenha pressionado um alfinete próximo ao seu destino e certifique-se de que diz \"Alfinete caído\" (caso contrário, o cálculo da distância pode não ser preciso) e, em seguida, compartilhe a localização com o aplicativo Essentials e comece a rastrear. + Add Destination + Edit Destination + Home, Office, etc. + Name + Save + Cancel + Resolving location… + Last Trip + Saved Destinations + No destinations saved yet. + Delete Destination + Tracking Now + Re-Start + Share coordinates (Dropped pin) from Google Maps to Essentials to save as a destination.\n\nThe distance shown is the direct distance to the destination, not the distance along the roads.\n\nTake all calculations of time and distance with a grain of salt as they are not always accurate. + Are we there yet? + Pause + Resume + Radius: %1$d m + Distance to target: %1$s + Last: %1$s + Never + To go + %1$d min + %1$d hr %2$d min + %1$s (%2$d%%) • %3$s to go + Congele aplicativos para impedir que sejam executados em segundo plano.\n\nEvite o consumo de bateria e o uso de dados congelando completamente os aplicativos quando não os estiver usando. Eles serão descongelados instantaneamente quando você os iniciar. Os aplicativos não aparecerão na gaveta de aplicativos e também não aparecerão para atualizações de aplicativos na Play Store enquanto estiverem congelados. + Um método de entrada personalizado que ninguém pediu.\n\nÉ apenas uma experiência. Vários idiomas podem não ter suporte, pois é uma implementação muito complexa e demorada. + Monitore os níveis de bateria de todos os seus dispositivos conectados.\n\nVeja o status da bateria de seus fones de ouvido Bluetooth, relógio e outros acessórios em um só lugar. Conecte-se ao aplicativo AirSync para exibir também o nível da bateria do seu Mac. + Adicione uma legenda/marca d\'água personalizada às suas fotos com dados EXIF ​​e informações do dispositivo.\n\nCompartilhe uma imagem diretamente de outro aplicativo no Essentials para adicionar facilmente uma marca d\'água. + Sincronize toda a sua próxima programação de calendário, independentemente das restrições das contas do Google que não permitem a adição de dispositivos wearOS devido a políticas profissionais ou escolares. \n\nCertifique-se de instalar o aplicativo complementar wearOS Essentials para exibir a programação no aplicativo, bem como em um bloco ou uma complicação. + Acompanhe as atualizações dos seus aplicativos instalados.\n\nReceba notificações sobre atualizações disponíveis, visualize registros de alterações e instale-as facilmente com um toque. + Adicione feedback tátil às suas chamadas.\n\nVibra quando uma chamada é conectada, desconectada ou aceita, fornecendo confirmação tátil sem olhar para a tela. + Alterne rapidamente entre os modos Som, Vibração e Silencioso.\n\nUm bloco conveniente para alterar o modo de campainha sem usar os botões ou configurações de volume. Você pode reordenar os modos ou desativar qualquer um, se não for necessário, para personalizar a alternância do bloco para o comportamento do ciclo. + Alterne facilmente o efeito de profundidade de desfoque no nível do sistema em todo o sistema operacional. + Ative ou desative balões de notificação flutuantes.\n\nAlterne rapidamente a configuração de todo o sistema para balões de conversa. + Oculte conteúdo confidencial na tela de bloqueio.\n\nAlterne se o conteúdo da notificação é mostrado ou oculto quando o dispositivo está bloqueado. + Alternar toque para ativar a funcionalidade.\n\nAtive ou desative a capacidade de ativar sua tela com um toque. + Ative o Always On Display.\n\nAtive ou desative rapidamente o Always On Display para visualizar informações rapidamente. + Controle automaticamente seu Always On Display com base em suas notificações. Quando uma mensagem ou alerta chega de um aplicativo selecionado, o AOD permanecerá ativado até você descartar a notificação, garantindo que você nunca perca informações importantes sem desperdiçar bateria quando nenhum alerta estiver presente. + Combine canais de áudio em mono.\n\nÚtil ao usar um único fone de ouvido ou para fins de acessibilidade. + Alterne a lanterna.\n\nUm toque longo abre os controles para ajuste de intensidade que pode precisar de implementação de hardware que alguns dispositivos podem não ter. + Mantenha a tela ativa durante o carregamento.\n\nEvita que a tela hiberne enquanto o dispositivo estiver conectado a uma fonte de energia adequada para desenvolvedores durante a depuração. + Alterne NFC.\n\nAtive ou desative rapidamente a Near Field Communication para pagamentos e emparelhamento. + Alternar brilho adaptável.\n\nAtiva ou desativa o ajuste automático de brilho da tela com base na luz ambiente. + Alternar DNS privado.\n\nPercorrer os modos de provedor de DNS desativado, automático e privado. + Alternar depuração USB.\n\nAtivar ou desativar o acesso à depuração ADB diretamente nas configurações rápidas. + Inicie a ferramenta conta-gotas para escolher as cores introduzidas no Android 17 BETA 2 + Otimize a vida útil da bateria limitando a carga máxima ou usando o carregamento adaptativo. Isso foi especialmente projetado para dispositivos Pixel para garantir longevidade e ciclos de carregamento saudáveis.\n\nCréditos: TebbeUbben/ChargeQuickTile + Download + + Tela desligada + Tela ativada + Desbloqueio de dispositivo + Carregador conectado + Carregador desconectado + Schedule + Time Period + Select Time + Select Time Range + Start Time + End Time + Repeat on + Carregando + Tela ativada + Vibrar + Mostrar notificação + Remover notificação + Ligue a lanterna + Desligue a lanterna + Alternar lanterna + Turn On Low Power Mode + Turn Off Low Power Mode + Papel de parede escuro + Esta ação requer que Shizuku ou Root ajustem o escurecimento do papel de parede do sistema. + Selecione o gatilho + Aplicativo + Automatize com base em aplicativo aberto + Selecione o estado + Selecione Ação + Em ação + Fora de ação + Cancelar + Salvar + Editar + Excluir + Habilitar + Desativar + Serviço de automação + Automações ativas + Monitorando eventos do sistema para suas automações + App Detection Service + App Detection Active + Monitoring app activity + Efeitos do dispositivo + Controle os efeitos no nível do sistema, como escala de cinza, supressão de AOD, escurecimento do papel de parede e modo noturno. + Tons de cinza + Suprimir exibição de ambiente + Papel de parede escuro + Modo noturno + Este recurso requer Android 15 ou superior. + Habilitado + Desabilitado + Modo de som + Esta ação permite alternar entre os modos Som, Vibração e Silêncio com base nos gatilhos. Requer acesso Não perturbe. + + Sameera Wijerathna + A caixa de ferramentas completa para Pixel e Androids + + Sistema + Personalizado + Específico do aplicativo + + Falha na autenticação + Mantenha pressionado um aplicativo na grade para adicionar um atalho + Aplicativo não encontrado ou desinstalado + + Atualizações de aplicativos + Notificações para novas atualizações de aplicativos + Atualização disponível + Nenhum dispositivo conectado + Desconhecido + 5G + 4G + 3G + Shizuku (Rika) + Shizuku (TuoZi) + Procurar + Recent Searches + No recent searches + Clear all + Necessário para bloquear o dispositivo quando são tentadas alterações de rede não autorizadas na tela de bloqueio. + Autenticar para acessar as configurações + %1$s Configurações + recurso + configurações + esconder + mostrar + visibilidade + Erro ao carregar aplicativos: %1$s + + vibração + tocar + sentir + + + rede + visibilidade + automático + esconder + + + restaurar + padrão + ícone + + + teclado + altura + preenchimento + háptico + entrada + + + luz + tocha + + + luz + tocha + pulso + notificação + + + acordado + desenvolvedor + poder + cobrar + + + brilho + notificação + liderado + + + redondo + forma + borda + + + seguro + privacidade + biométrico + face + impressão digital + + + som + acessibilidade + ouvir + + + ficar + sobre + tempo esgotado + + + tocar + acordar + mostrar + + + temporizador + espere + tempo esgotado + + Tema sempre escuro + Tema preto como breu + Histórico da área de transferência + Long press for symbols + Accented characters + + lista + selecionador + seleção + + + animação + visual + olhar + + + quieto + ignorar + filtro + + + automação + auto + trancar + + + adb + USB + depurar + + + borrão + vidro + vinheta + + + flutuador + janela + sobreposição + + + sempre + mostrar + relógio + + + áudio + mudo + volume + + + azul + filtro + auto + + + congelar + shizuku + + + manual + agora + shizuku + + + proximidade + sensor + face + abaixo + + + trocar + mestre + + + vibração + sentir + + + bateria + cobrar + otimização + pixel + + + Inverter seleção + Mostrar aplicativos do sistema + + Você está atualizado + Esta é uma versão de pré-lançamento e pode ser instável. + Notas de versão %1$s + Ver no GitHub + Baixar APK + + Nenhum + Sutil + Dobro + Clique + Marcação + + Desligar + Brilho da lanterna + + + Desenvolvido por %1$s\ncom ❤\uFE0F de \uD83C\uDDF1\uD83C\uDDF0 + Site + Contato + Telegrama + Apoiar + Outros aplicativos + AirSync + ZenZero + Tela + Tarefas + Zero + + Ajuda e guias + Precisa de mais suporte? Estenda a mão, + Colapso + Expandir + Grupo de Apoio + E-mail + Enviar e-mail + Nenhum aplicativo de e-mail disponível + Passo %1$d Imagem + + Permissões de acessibilidade, notificação e sobreposição + Você pode receber esta mensagem de acesso negado se tentar conceder permissões confidenciais, como acessibilidade, ouvinte de notificação ou permissões de sobreposição. Para concedê-lo, verifique os passos abaixo. + 1. Vá para a página de informações do aplicativo Essentials. + 2. Abra o menu de 3 pontos e selecione \'Permitir configurações restritas\'. Talvez seja necessário autenticar com biometria. Uma vez feito isso, tente conceder a permissão novamente. + Shizuku + Shizuku é uma ferramenta poderosa que permite que aplicativos usem APIs do sistema diretamente com ADB ou permissões de root. É necessário para recursos como modo mínimo do Maps e App Freezer. E insistirá em conceder algumas permissões, como WRITE_SECURE_SETTINGS. \n\nMas a versão do Shizuku na Play Store pode estar desatualizada e provavelmente ficará inutilizável em versões recentes do Android, portanto, nesse caso, obtenha a versão mais recente no github ou um fork atualizado dele. + Modo de economia de energia do Maps + Este recurso aciona automaticamente o modo de economia de energia do Google Maps, que atualmente é exclusivo da série Pixel 10. Um membro da comunidade descobriu que ele ainda pode ser usado em qualquer dispositivo Android iniciando a atividade minMode dos mapas com privilégios de root. \n\nE então, eu o automatizei com Tasker para acionar automaticamente quando a tela desliga durante uma sessão de navegação e então consegui fazer o mesmo com apenas permissões de tempo de execução do Shizuku. \n\nEle deve ser mostrado no AOD da série Pixel 10, por causa disso, você poderá ver uma mensagem ocasional aparecendo na tela informando que ele não suporta o modo paisagem. Isso não pode ser evitado pelo aplicativo e você pode ignorar. + Modo de som silencioso + Você deve ter notado que o modo silencioso também aciona o DND. \n\nIsso se deve à forma como o Android o implementou, pois mesmo que usemos a mesma API para mudar para o modo vibratório, por algum motivo ele ativa o DND junto com o modo silencioso e isso não é evitável neste momento. :( + O que é congelar? + Faça uma pausa e fique longe das distrações dos aplicativos enquanto economiza um pouco de energia, evitando que os aplicativos sejam executados em segundo plano. Adequado para aplicativos raramente usados. \n\nNão recomendado para quaisquer serviços de comunicação, pois eles não irão notificá-lo em caso de emergência, a menos que você os descongele. \n\nAltamente recomendado não congelar aplicativos do sistema, pois eles podem levar à instabilidade do sistema. Prossiga com cautela, você foi avisado. \n\nInspirado por Hail <3 + O bloqueio de aplicativos e a segurança de tela bloqueada são realmente seguros? + Absolutamente não. \n\nQualquer aplicativo de terceiros não pode interferir 100% nas interações regulares do dispositivo e até mesmo o bloqueio do aplicativo é apenas uma sobreposição acima dos aplicativos selecionados para evitar a interação com eles. Existem soluções alternativas e não são infalíveis. \n\nO mesmo acontece com o recurso de segurança de tela bloqueada que detecta alguém tentando interagir com os blocos de rede que, por algum motivo, ainda estão acessíveis para qualquer pessoa em Pixels. Portanto, se eles se esforçarem o suficiente, ainda poderão alterá-los e, especialmente, se você tiver um bloco QS de modo de voo adicionado, este aplicativo não poderá impedir interações com ele. \n\nEsses recursos são feitos apenas como experimentos para uso leve e nunca seriam recomendados como soluções fortes de segurança e privacidade. \n\nSeguro alternativas:\n - Bloqueio de aplicativo: espaço privado e pasta segura em Pixels e Samsung\n - Impedir o acesso a redes móveis: certifique-se de que a proteção contra roubo e as configurações off-line/desligamento encontre meu dispositivo estejam ativadas. Você também pode pesquisar o Graphene OS. + Ícones da barra de status + Você pode notar que mesmo depois de redefinir os ícones da barra de status, alguns ícones, como rotação do dispositivo e ícones de fones de ouvido com fio, podem permanecer visíveis. Isso se deve à forma como a lista negra statubar é implementada no Android e como seu OEM pode tê-la personalizado. \nVocê pode precisar de mais ajustes. \n\nAlém disso, nem todas as opções de visibilidade de ícones podem funcionar, pois dependem das implementações e disponibilidade do OEM. + A iluminação de notificação não funciona + Depende do OEM. Alguns, como o OneUI, parecem não permitir sobreposições acima do AOD, impedindo a exibição dos efeitos de iluminação. Nesse caso, tente a exibição do ambiente como solução alternativa. + O remapeamento do botão não funciona enquanto a exibição está desligada + Alguns OEMs limitam os relatórios do serviço de acessibilidade quando a tela está realmente desligada, mas eles ainda podem funcionar enquanto o AOD está ligado. \nNesse caso, você pode usar remapeamentos de botão com o AOD ativado, mas não desativado. \n\nComo solução alternativa, você precisará usar as permissões do Shizuku e ativar o \'Use Shizuku ou Root\' toggle nas configurações de remapeamento do botão que identifica e ouve eventos de entrada de hardware.\nIsso não é garantido para funcionar em todos os dispositivos e necessidades testando.\n\nE mesmo que\'estive ativado, o método Shizuku só será usado quando for\'s necessário. Caso contrário, ele sempre retornará para Acessibilidade, que também controla o bloqueio da entrada real durante um toque longo. + O brilho da lanterna não funciona + Apenas um número limitado de dispositivos tem suporte de hardware e software para ajustar a intensidade da lanterna. \n\n\'A versão mínima do Android é 13 (SDK33).\nO controle de brilho da lanterna suporta apenas HAL versão 3.8 e superior, portanto, entre os dispositivos suportados, os mais recentes (por exemplo, Pixel 6/7, Samsung S23, etc.)\'\npolodarb/Lanterna-Tiramisu + Que diabos é esse aplicativo? + Boa pergunta,\n\nSempre quis extrair o máximo dos meus dispositivos à medida que\'Sou um usuário rooteado desde que comprei meu primeiro dispositivo Project Treble. E eu\'Tenho adorado o aplicativo Tasker, que é como um deus quando se trata de automação e utiliza todas as APIs e recursos internos possíveis do Android.\n\nPortanto, não estou desenraizado e de volta à experiência beta do Android e queria aproveitar ao máximo o que é possível com determinados privilégios. É melhor compartilhá-los. Então, com meu conhecimento iniciante em Kotlin Jetpack e com o apoio de muitas ferramentas de pesquisa e assistência e também da grande comunidade, construí um aplicativo completo contendo tudo o que eu queria que estivesse em meu Android com determinadas permissões. E aqui está.\n\nSolicitações de recursos são bem-vindas. Vou considerar e ver se elas são viáveis ​​com as permissões disponíveis e minhas habilidades. Hoje em dia o que não é possível. :)\n\nPor que não na Play Store?\nEu não\'Não quero arriscar que minha conta de desenvolvedor seja banida devido às permissões e APIs altamente confidenciais e internas usadas no aplicativo. Mas com a forma como o sideload do Android está indo, vamos\'vamos ver o que temos que fazer. Eu entendo as preocupações de aplicativos transferidos serem maliciosos.\nJá que estamos no assunto, confira meu outro aplicativo AirSync se você for um usuário Mac + Android. *plugue sem vergonha*\n\nAproveite, continue construindo! (っ◕‿◕)っ + + Relatório de bug copiado para a área de transferência + Relatório de bug + Compartilhar registros + Incluir registros e detalhes + Informações do dispositivo + Relatório Bruto + Abrir problema do GitHub + Relatório por e-mail + Copiar para a área de transferência + Relatório de erros essenciais + Enviar por + + Já chegamos? + Alertas de destino próximo + Abra o Google Maps, escolha um local e compartilhe-o no Essentials. + Raio de alerta: %d m + Localização + Usado para detectar a chegada ao seu destino. + Localização de fundo + Necessário para monitorar sua chegada enquanto o aplicativo está fechado ou a tela desligada. + Destino alcançado! + Você chegou ao seu destino. + Local de processamento… + DISTÂNCIA RESTANTE + Calculando… + Stop Tracking + Destino pronto + Comece a rastrear + Ver mapa + Claro + Sem destino + Abrir mapas + Permissão de alarme em tela cheia + Necessário para ativar seu dispositivo na chegada. Toque para conceder. + %1$d eu + %1$,1f km + Travel alarm active + %1$s restante (%2$d%%) + Progresso da viagem + Mostra a distância em tempo real até o destino + Destino próximo + Prepare-se para descer + Liberar + Conjunto de destino: %1$.4f, %2$.4f + Usar raiz + Em vez de Shizuku + Acesso root não disponível. Por favor, verifique seu gerenciador root. + + Teclado + Chaves + Personalize o layout e o comportamento + Altura do teclado + Ajuste o tamanho vertical total do teclado + Preenchimento inferior + Adicione espaço abaixo do teclado + Feedback tátil + Vibrar ao pressionar a tecla + Teste o teclado + Altura do teclado + Preenchimento inferior + Feedback tátil + Redondeza chave + Mover funções para baixo + Funções de preenchimento lateral + Força do feedback tátil + Formato do teclado + Redondo + Plano + Inverso + Baterias + Monitore os níveis de bateria do seu dispositivo + Status da bateria + Conecte-se ao AirSync + Exibir a bateria do seu dispositivo Mac conectado no AirSync + Baixe o aplicativo AirSync + Necessário para sincronização de bateria do Mac + Notificação de bateria + Notificação persistente de status da bateria + Este aplicativo mostra a porcentagem da bateria dos seus dispositivos Mac e Bluetooth conectados. Você pode configurar quais dispositivos serão exibidos nas configurações do widget de bateria. + Replique a experiência do widget de bateria na sua aba de notificações. Ele mostrará os níveis de bateria de todos os seus dispositivos conectados em uma única notificação persistente, atualizada em tempo real. Isso inclui seu Mac (via AirSync) e acessórios Bluetooth. + Notificação de status da bateria + Notificação persistente mostrando os níveis de bateria dos dispositivos conectados + Dispositivos próximos + Necessário para detectar e recuperar informações da bateria de acessórios Bluetooth + + Copiar código + Abrir página de login + Faça login para estender os limites de chamadas de API + Aguardando autorização... + Faça login com GitHub + sair + Perfil + + Notas de versão + Nenhum repositório rastreado ainda + Nenhum aplicativo vinculado + Atualizado %1$s + + agora mesmo + Today + Yesterday + %1$dhá muito tempo + %1$dh atrás + %1$dd atrás + %1$d days ago + %1$d weeks ago + %1$dhá cerca de um mês + %1$d months ago + %1$dvocê atrás + Tentar novamente + Iniciar login + Solicitando código do dispositivo... + 1. Copie seu código: + 2. Cole o código no GitHub: + APKs encontrados + LEIA-ME + Atualizar + + Bloco de modo de som + Bloco QS para alternar o modo de som + Mostrar controle deslizante + Mostrar controle deslizante de volume no bloco + Comportamento do Ciclo + Escolha os modos para percorrer + Som e sensação tátil + Volume e recursos táteis + Segurança e privacidade + Proteja e proteja seu dispositivo + Notificações e Alertas + Nunca perca suas prioridades + Entrada e ações + Controle seu dispositivo com facilidade + Widgets + De relance na tela inicial + Mostrar + Recursos visuais para aprimorar sua experiência + Assistir + Integrações com WearOS + Nenhum relógio detectado + Parece que você não tem o aplicativo complementar Essentials Wear instalado no seu relógio. + Instalar companheiro + Download from GitHub + Install on Watch + To use Watch features, you need to install the Essentials companion app on your Wear OS watch. Click the button below to open the Play Store directly on your watch. + Open on Watch + Interação + Interface + Mostrar + Proteção + Accessibility + Connectivity + Privacy + Utilities + abc + \?#/ + Kaomoji + Joy + Love + Embarassment + Sympathy + Dissatisfaction + Anger + Apologizing + Bear + Bird + Cat + Confusion + Dog + Doubt + Enemies + Faces + Fear + Fish + Food + Friends + Games + Greeting + Hiding + Hugging + Indifference + Magic + Music + Nosebleeding + Pain + Pig + Rabbit + Running + Sadness + Sleeping + Special + Spider + Surprise + Weapons + Winking + Writing + Ei! Você pode verificar as atualizações nas configurações do aplicativo, não há necessidade de adicionar aqui XD + Exportar + Importar + Repositórios exportados com sucesso + Falha ao exportar repositórios + Repositórios importados com sucesso + Falha ao importar repositórios + Aplicativos + Escala e animações + Ajuste a escala e as animações do sistema + Screen Refresh Rate + Set a fixed or ranged screen refresh rate + Texto + Escala de fonte + Peso da fonte + Reiniciar + Escala + Menor largura + É necessária permissão de Shizuku para ajustar a escala + Conceder permissão + Animações + Escala de duração do animador + Escala de animação de transição + Escala de animação de janela + Ajuste a escala da fonte, o peso e as velocidades de animação em todo o sistema. Observe que algumas configurações podem exigir permissões avançadas ou a reinicialização do dispositivo para determinados aplicativos para refletir as alterações. \n\nShizuku adicional ou permissão de root podem ser necessárias para ajustes de escala + Set a custom screen refresh rate using Shizuku. Fixed mode locks both the minimum and peak refresh rate to one value, while range mode lets you define separate minimum and peak values. Reset clears the custom override and returns the device to its system-managed refresh behavior. + Mode + Refresh Rate + Fixed + Range + Fixed refresh rate + Set both minimum and peak refresh rate to the same value + Minimum refresh rate + Choose the lowest refresh rate the display may use + Peak refresh rate + Choose the highest refresh rate the display may use + System + Remove the custom override and return to the system default refresh behavior + Shizuku permission required to adjust refresh rate + Refresh Rate Mode + Switch between fixed and ranged refresh rate control + Fixed Refresh Rate + Lock the display to one custom refresh rate + Refresh Rate Range + Configure separate minimum and peak refresh rates + Reset Refresh Rate + Clear the custom refresh rate override + Forçar desligamento do AOD + Forçar o desligamento do AOD quando não houver notificações. Requer permissão de acessibilidade. + Acessibilidade automática + Concede automaticamente a permissão de acessibilidade na inicialização do aplicativo, caso esteja faltando, usando WRITE_SECURE_SETTINGS. + Ajuda e guias + Seu Android + Armazenar + Memória + Usar desfoque + Habilite elementos de desfoque progressivo na IU + O desfoque está desativado neste dispositivo para evitar um bug de exibição conhecido em dispositivos Samsung com Android 15 ou inferior. + + Nenhum aplicativo selecionado para congelar. + Comece + Nova Automação + Adicionar repositório + + Describe the issue or provide feedback… + Contact email + Send Feedback + Feedback sent successfully! Thanks for helping us improve the app. + Alternatively + + Diagnostics + Device Check + Get ready to be flashbanged! + Abort + Continue + Your Essentials trial has expired + Your free trial period of Essentials has ended. Access to advanced features like Button Remap, App Freezing, and DIY Automations with Premium. + What\'s in Premium? + April Fools! + Just kidding, Essentials is and will always be free and open source. Enjoy! (っ◕‿◕)っ + System + Ripple Animation + Bottom + Center + Custom + Shizuku or Root permission is required for System lighting effects + Made for Google Pixel. Might not be available on other devices. + + Live wallpaper + Set custom video as live wallpaper + Live Wallpaper + A video live wallpaper that plays when unlocked. + Play when + Unlock + Screen on + Video + Apply + Add video + Custom video + Set your favorite video as your home screen wallpaper. Choose between triggering the animation upon unlocking or simply whenever the screen turns on for a dynamic experience. + + Shut-Up! + I know what I\'m doing + Shut-Up! bypasses app restrictions and checks for developer options, accessibility and debugging by dynamically hiding them when you launch sensitive apps. \n\nUse the custom shortcuts to ensure settings are hidden before the app launches. + Select Apps + Choose apps to apply Shut-Up! logic + Shut-Up! Settings + Shut-Up! Features + Disable Developer Options + Disable USB Debugging + Disable Wireless Debugging + Disable Accessibility Services + Shortcut created for %s + Disable rotation suggestion + Do not show the rotation button when auto-rotate is off + App got shut up + Shut up features returned + Auto Freeze + %1$s will be archived in %2$d seconds + Freeze now + Abort + + Lock screen clock + Customize lock screen clock on Pixels + Change the lock screen clock style on Pixels. Select from the available clock faces provided by the System UI. Some old styles might not be available. + Big Number + Calligraphy + Flex + Growth + Handwritten + Inflate + Metro + Number Overlap + Weather + Select Clock Style + Current + Apply + Clock style applied + WRITE_SECURE_SETTINGS permission required + Default + Weight + Width + Grade + Roundness + Lightness + Variation + Color + Style & Font + About + Default + Red + Green + Blue + Yellow + Orange + Purple + Pink + Teal + + Device Specs Retired + Inaccurate GSMArena-powered specifications have been removed to keep device insights clean, fast, and completely reliable. + App Updates Migrated + The Apps tab is now fully integrated into the Your Android feature, making it easier to manage all your app updates in one cohesive hub. + diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index a44b432d4..c59e4e531 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -20,7 +20,8 @@ - + + diff --git a/crowdin.yml b/crowdin.yml index a74c50316..69d90ec91 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -7,4 +7,4 @@ files: ignore: - /app/src/main/res/values-v24/ - /app/src/main/res/values-night/ - translation: /app/src/main/res/values-%two_letters_code%/strings.xml + translation: /app/src/main/res/values-%android_code%/strings.xml