Showing preview only (399K chars total). Download the full file or copy to clipboard to get everything.
Repository: GustavoASantos/Noti
Branch: main
Commit: 7f4c1fbac4e0
Files: 136
Total size: 337.8 KB
Directory structure:
gitextract_91b8wrhl/
├── .gitignore
├── README.md
├── app/
│ ├── .gitignore
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── gustavoas/
│ │ └── noti/
│ │ ├── ProgressBarAppsAdapter.kt
│ │ ├── ProgressBarAppsRepository.kt
│ │ ├── SettingsActivity.kt
│ │ ├── Utils.kt
│ │ ├── fragments/
│ │ │ ├── BasePreferenceFragment.kt
│ │ │ ├── CircularBarFragment.kt
│ │ │ ├── LinearBarFragment.kt
│ │ │ ├── PerAppSettingsFragment.kt
│ │ │ └── SettingsFragment.kt
│ │ ├── model/
│ │ │ ├── DeviceConfiguration.kt
│ │ │ ├── ProgressBarApp.kt
│ │ │ └── ProgressNotification.kt
│ │ ├── notifications/
│ │ │ ├── DownloadProgressBar.kt
│ │ │ ├── GoogleTimerProgressBar.kt
│ │ │ ├── MediaProgressBar.kt
│ │ │ ├── PercentageProgressBar.kt
│ │ │ ├── ProgressBarNotification.kt
│ │ │ └── TimedProgressBar.kt
│ │ ├── preferences/
│ │ │ ├── BannerPreference.kt
│ │ │ ├── BarStylesListPreference.kt
│ │ │ └── SeekBarPreference.kt
│ │ └── services/
│ │ ├── AccessibilityService.kt
│ │ ├── FullscreenDetectionService.kt
│ │ └── NotificationListenerService.kt
│ └── res/
│ ├── color/
│ │ └── outlined_button_selector.xml
│ ├── drawable/
│ │ ├── circular_mask.xml
│ │ ├── ic_accessibility.xml
│ │ ├── ic_apps.xml
│ │ ├── ic_bug_report.xml
│ │ ├── ic_calentile.xml
│ │ ├── ic_circular_progress_bar.xml
│ │ ├── ic_close.xml
│ │ ├── ic_colors.xml
│ │ ├── ic_contrast.xml
│ │ ├── ic_download.xml
│ │ ├── ic_download_filled.xml
│ │ ├── ic_fullscreen.xml
│ │ ├── ic_height.xml
│ │ ├── ic_horizontal.xml
│ │ ├── ic_info.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── ic_linear_progress_bar.xml
│ │ ├── ic_lockscreen.xml
│ │ ├── ic_margin_top.xml
│ │ ├── ic_music.xml
│ │ ├── ic_music_filled.xml
│ │ ├── ic_notification.xml
│ │ ├── ic_notification_bar.xml
│ │ ├── ic_overlay.xml
│ │ ├── ic_palette.xml
│ │ ├── ic_preview.xml
│ │ ├── ic_progress_bar_style.xml
│ │ ├── ic_rounded_corners.xml
│ │ ├── ic_share.xml
│ │ ├── ic_size.xml
│ │ ├── ic_sumup.xml
│ │ ├── layout_bg.xml
│ │ ├── selector_download.xml
│ │ ├── selector_music.xml
│ │ └── splashscreen.xml
│ ├── drawable-v31/
│ │ └── splashscreen.xml
│ ├── layout/
│ │ ├── activity_main.xml
│ │ ├── advanced_style_dialog.xml
│ │ ├── app_item.xml
│ │ ├── banner_preference.xml
│ │ ├── button_toggle_group.xml
│ │ ├── fragment_per_app_settings.xml
│ │ ├── material_switch.xml
│ │ ├── per_app_settings_footer.xml
│ │ ├── progress_bar.xml
│ │ └── seekbar_preference.xml
│ ├── layout-land/
│ │ └── button_toggle_group.xml
│ ├── menu/
│ │ └── settings_menu.xml
│ ├── mipmap-anydpi-v26/
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ ├── resources.properties
│ ├── values/
│ │ ├── arrays.xml
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ ├── values-ar/
│ │ └── strings.xml
│ ├── values-ar-rSA/
│ │ └── strings.xml
│ ├── values-bg/
│ │ └── strings.xml
│ ├── values-cs/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-es-rMX/
│ │ └── strings.xml
│ ├── values-fr/
│ │ └── strings.xml
│ ├── values-fr-rCA/
│ │ └── strings.xml
│ ├── values-hu/
│ │ └── strings.xml
│ ├── values-in/
│ │ └── strings.xml
│ ├── values-it/
│ │ └── strings.xml
│ ├── values-iw/
│ │ └── strings.xml
│ ├── values-ja/
│ │ └── strings.xml
│ ├── values-ka/
│ │ └── strings.xml
│ ├── values-lt/
│ │ └── strings.xml
│ ├── values-ml/
│ │ └── strings.xml
│ ├── values-nb-rNO/
│ │ └── strings.xml
│ ├── values-night/
│ │ ├── colors.xml
│ │ └── themes.xml
│ ├── values-pl/
│ │ └── strings.xml
│ ├── values-pt-rPT/
│ │ └── strings.xml
│ ├── values-ro/
│ │ └── strings.xml
│ ├── values-ru/
│ │ └── strings.xml
│ ├── values-sk/
│ │ └── strings.xml
│ ├── values-sl/
│ │ └── strings.xml
│ ├── values-ta/
│ │ └── strings.xml
│ ├── values-tr/
│ │ └── strings.xml
│ ├── values-uk/
│ │ └── strings.xml
│ ├── values-v31/
│ │ └── arrays.xml
│ ├── values-vi/
│ │ └── strings.xml
│ ├── values-zh-rCN/
│ │ └── strings.xml
│ ├── values-zh-rTW/
│ │ └── strings.xml
│ └── xml/
│ ├── accessibility_service_config.xml
│ ├── backup_rules.xml
│ ├── circular_bar_preferences.xml
│ ├── data_extraction_rules.xml
│ ├── linear_bar_preferences.xml
│ └── preferences.xml
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── renovate.json
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.iml
.gradle
/local.properties
/.idea
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
================================================
FILE: README.md
================================================
# Noti Progress Bar
[<img alt="banner" src="Images/Banner.png" />](https://play.google.com/store/apps/details?id=com.gustavoas.noti)
> Noti lets you easily track the progress of downloads, music and more directly from your status bar.
## ✨ Features
+ Track downloads, music, and timers from Google Clock.
+ Customizable progress bar or progress circle.
+ Adjustable progress circle to fit your phone's hole punch camera.
+ Automatically hides when in full screen mode.
+ Display Noti in the lockscreen by enabling the accessibility service.
+ Filter which apps you want to track.
+ Customize Noti on a per-app basis.
## ⬇️ Download
[<img alt="Get it on Google Play" height="100" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png" />](https://play.google.com/store/apps/details?id=com.gustavoas.noti)
## 💜 Help Translate
<a href="https://hosted.weblate.org/engage/noti-progress-bar/">
<img src="https://hosted.weblate.org/widget/noti-progress-bar/strings/287x66-white.png" alt="Translation status" />
</a>
## 🔑 License
The source code for Noti is available under the GPL-3.0 License. However, distributing the compiled application anywhere, including on Google Play, requires explicit written permission from the original author.
================================================
FILE: app/.gitignore
================================================
/build
/release/
/src/debug/
/google-services.json
================================================
FILE: app/build.gradle
================================================
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.gms.google-services'
id 'kotlin-parcelize'
}
android {
namespace 'com.gustavoas.noti'
compileSdk 35
defaultConfig {
applicationId "com.gustavoas.noti"
minSdk 21
targetSdk 34
versionCode 90
versionName "2.0"
versionCode 95
versionName "2.2"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
applicationIdSuffix ".dogfood"
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
androidResources {
generateLocaleConfig true
}
}
dependencies {
implementation platform('com.google.firebase:firebase-bom:33.13.0')
implementation 'androidx.core:core-ktx:1.16.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.palette:palette-ktx:1.0.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation 'androidx.recyclerview:recyclerview:1.4.0'
implementation 'com.github.kizitonwose.colorpreference:support:1.1.0'
implementation 'com.github.kizitonwose.colorpreference:core:1.1.0'
implementation 'com.github.eltos:simpledialogfragments:v3.7'
implementation 'com.google.firebase:firebase-storage'
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
android:minSdkVersion="30"/>
<uses-permission android:name="android.permission.VIBRATE" />
<uses-sdk tools:overrideLibrary="androidx.core.splashscreen"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:enableOnBackInvokedCallback="true"
android:theme="@style/Theme.NotiProgressBar"
tools:targetApi="tiramisu">
<activity
android:name=".SettingsActivity"
android:exported="true"
android:label="@string/app_name_short"
android:theme="@style/SplashScreen">
<intent-filter>
<action android:name="android.intent.action.APPLICATION_PREFERENCES"/>
<category android:name="android.intent.category.DEFAULT"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".services.AccessibilityService"
android:label="@string/app_name"
android:exported="false"
android:theme="@style/Theme.NotiProgressBar"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
<service
android:name=".services.NotificationListenerService"
android:label="@string/notificationListenerDescription"
android:exported="false"
android:theme="@style/Theme.NotiProgressBar"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
<service
android:name=".services.FullscreenDetectionService"
android:exported="false" />
</application>
</manifest>
================================================
FILE: app/src/main/java/com/gustavoas/noti/ProgressBarAppsAdapter.kt
================================================
package com.gustavoas.noti
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.CheckBox
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.edit
import androidx.core.graphics.drawable.toDrawable
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButtonToggleGroup
import com.gustavoas.noti.Utils.dpToPx
import com.gustavoas.noti.Utils.getApplicationIcon
import com.gustavoas.noti.Utils.getApplicationName
import com.gustavoas.noti.Utils.getColorForApp
import com.gustavoas.noti.Utils.showColorDialog
import com.gustavoas.noti.model.ProgressBarApp
import com.gustavoas.noti.services.AccessibilityService
class ProgressBarAppsAdapter(
private val fragment: Fragment,
private val context: Context,
private val apps: ArrayList<ProgressBarApp>,
private val appsRepository: ProgressBarAppsRepository
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val VIEW_TYPE_HEADER = 0
private val VIEW_TYPE_ITEM = 1
private val VIEW_TYPE_FOOTER = 2
override fun getItemViewType(position: Int): Int {
return when (position) {
0 -> VIEW_TYPE_HEADER
itemCount - 1 -> VIEW_TYPE_FOOTER
else -> VIEW_TYPE_ITEM
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIEW_TYPE_HEADER -> {
val view = LayoutInflater.from(context).inflate(R.layout.button_toggle_group, parent, false)
HeaderViewHolder(view)
}
VIEW_TYPE_FOOTER -> {
val view = LayoutInflater.from(context).inflate(R.layout.per_app_settings_footer, parent, false)
FooterViewHolder(view)
}
else -> {
val view = LayoutInflater.from(context).inflate(R.layout.app_item, parent, false)
ItemViewHolder(view)
}
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder !is ItemViewHolder) return
with(apps[position - 1]) {
holder.appName.text = getApplicationName(context, packageName) ?: packageName
val appIcon = getApplicationIcon(context, packageName) ?: Color.TRANSPARENT.toDrawable()
val appIconSize = dpToPx(context, 36)
appIcon.setBounds(0, 0, appIconSize, appIconSize)
holder.appName.setCompoundDrawables(appIcon, null, null, null)
holder.toggle.isChecked = showProgressBar
holder.toggle.setOnCheckedChangeListener { _, isChecked ->
showProgressBar = isChecked
appsRepository.updateApp(this)
if (!isChecked) {
val intent = Intent(context, AccessibilityService::class.java)
intent.putExtra("packageName", packageName)
intent.putExtra("removal", true)
context.startService(intent)
}
}
holder.background.setOnClickListener {
holder.toggle.toggle()
}
val barColor = getColorForApp(context, this)
holder.colorPicker.setBackgroundColor(barColor)
holder.colorPicker.setOnClickListener {
showColorDialog(
fragment,
barColor,
(position - 1).toString(),
!useDefaultColor,
)
}
}
}
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
super.onViewRecycled(holder)
if (holder !is ItemViewHolder) return
holder.toggle.setOnCheckedChangeListener(null)
}
override fun getItemCount(): Int = apps.size + 2
class HeaderViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val toggleGroup: MaterialButtonToggleGroup = itemView.findViewById(R.id.toggleGroup)
init {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this.itemView.context)
val enableDownloads = sharedPrefs.getBoolean("showForDownloads", true)
val enableMedia = sharedPrefs.getBoolean("showForMedia", true)
if (enableDownloads) {
toggleGroup.check(R.id.toggleDownloads)
}
if (enableMedia) {
toggleGroup.check(R.id.toggleMedia)
}
toggleGroup.addOnButtonCheckedListener { _, checkedId, isChecked ->
when (checkedId) {
R.id.toggleDownloads -> {
sharedPrefs.edit { putBoolean("showForDownloads", isChecked) }
}
R.id.toggleMedia -> {
sharedPrefs.edit { putBoolean("showForMedia", isChecked) }
}
}
}
}
}
class ItemViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val appName: TextView = view.findViewById(R.id.app_name)
val toggle: CheckBox = view.findViewById(R.id.checkbox)
val background: LinearLayout = view.findViewById(R.id.item_container)
val colorPicker: Button = view.findViewById(R.id.color_picker)
}
class FooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/ProgressBarAppsRepository.kt
================================================
package com.gustavoas.noti
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import com.gustavoas.noti.model.ProgressBarApp
class ProgressBarAppsRepository(context: Context) :
SQLiteOpenHelper(context, DATABASE_NAME, null, 3) {
companion object {
const val DATABASE_NAME = "progressBarApps"
const val TABLE_NAME = "apps"
const val COLUMN_PACKAGE = "package_name"
const val COLUMN_SHOW_PROGRESS = "show_progress"
const val COLUMN_COLOR = "color"
const val COLUMN_USE_DEFAULT = "default_color"
const val COLUMN_USE_MATERIAL_YOU = "material_you_color"
private val apps: MutableList<ProgressBarApp> = mutableListOf()
}
init {
if (apps.isEmpty()) {
apps.addAll(getAllApps())
}
}
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(
"CREATE TABLE IF NOT EXISTS $TABLE_NAME (" +
"$COLUMN_PACKAGE TEXT PRIMARY KEY," +
"$COLUMN_SHOW_PROGRESS INTEGER NOT NULL DEFAULT 1," +
"$COLUMN_COLOR INTEGER DEFAULT NULL," +
"$COLUMN_USE_DEFAULT INTEGER NOT NULL DEFAULT 1," +
"$COLUMN_USE_MATERIAL_YOU INTEGER NOT NULL DEFAULT 0" +
")"
)
val knownApps = listOf(
"com.google.android.deskclock",
"com.android.chrome",
"com.duckduckgo.mobile.android",
"com.android.vending",
"com.epicgames.portal",
"code.name.monkey.retromusic",
"com.google.android.apps.youtube.music",
"com.spotify.music",
)
knownApps.forEach { packageName ->
db?.execSQL(
"INSERT INTO $TABLE_NAME ($COLUMN_PACKAGE) VALUES ('$packageName')"
)
}
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
if (oldVersion < 2) {
db?.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN $COLUMN_COLOR INTEGER NOT NULL DEFAULT 1")
}
if (oldVersion < 3) {
db?.execSQL("ALTER TABLE $TABLE_NAME RENAME TO ${TABLE_NAME}_old")
db?.execSQL(
"CREATE TABLE $TABLE_NAME (" +
"$COLUMN_PACKAGE TEXT PRIMARY KEY," +
"$COLUMN_SHOW_PROGRESS INTEGER NOT NULL DEFAULT 1," +
"$COLUMN_COLOR INTEGER DEFAULT NULL," +
"$COLUMN_USE_DEFAULT INTEGER NOT NULL DEFAULT 1," +
"$COLUMN_USE_MATERIAL_YOU INTEGER NOT NULL DEFAULT 0" +
")"
)
db?.execSQL(
"INSERT INTO $TABLE_NAME ($COLUMN_PACKAGE, $COLUMN_SHOW_PROGRESS, $COLUMN_COLOR) " +
"SELECT $COLUMN_PACKAGE, $COLUMN_SHOW_PROGRESS, $COLUMN_COLOR FROM ${TABLE_NAME}_old"
)
db?.execSQL("DROP TABLE ${TABLE_NAME}_old")
db?.execSQL("UPDATE $TABLE_NAME SET " +
"$COLUMN_USE_DEFAULT = CASE WHEN $COLUMN_COLOR = 1 THEN 1 ELSE 0 END, " +
"$COLUMN_USE_MATERIAL_YOU = CASE WHEN $COLUMN_COLOR = 2 THEN 1 ELSE 0 END, " +
"$COLUMN_COLOR = CASE WHEN $COLUMN_COLOR IN (1, 2) THEN NULL ELSE $COLUMN_COLOR END"
)
}
}
fun addApp(app: ProgressBarApp): ProgressBarApp {
writableDatabase.execSQL(
"INSERT OR IGNORE INTO $TABLE_NAME ($COLUMN_PACKAGE) VALUES (?)",
arrayOf(app.packageName)
)
apps.firstOrNull {
it.packageName == app.packageName
}?.let {
return it
}
apps.add(app)
return app
}
fun updateApp(app: ProgressBarApp) {
writableDatabase.execSQL(
"UPDATE $TABLE_NAME " +
"SET $COLUMN_SHOW_PROGRESS = ?, " +
"$COLUMN_COLOR = ?, " +
"$COLUMN_USE_DEFAULT = ?, " +
"$COLUMN_USE_MATERIAL_YOU = ? " +
"WHERE $COLUMN_PACKAGE = ?",
arrayOf(
if (app.showProgressBar) 1 else 0,
app.color,
if (app.useDefaultColor) 1 else 0,
if (app.useMaterialYouColor) 1 else 0,
app.packageName
)
)
apps.indexOfFirst {
it.packageName == app.packageName
}.let { index ->
if (index != -1) {
apps[index] = app
}
}
}
fun showProgressForApp(packageName: String): Boolean? {
apps.firstOrNull {
it.packageName == packageName
}?.let {
return it.showProgressBar
}
readableDatabase.rawQuery(
"SELECT $COLUMN_SHOW_PROGRESS FROM $TABLE_NAME WHERE $COLUMN_PACKAGE = ?",
arrayOf(packageName)
).use { cursor ->
if (cursor.moveToFirst()) {
return cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_SHOW_PROGRESS)) == 1
}
}
return null
}
fun getApp(packageName: String): ProgressBarApp? {
apps.firstOrNull {
it.packageName == packageName
}?.let {
return it
}
readableDatabase.rawQuery(
"SELECT * FROM $TABLE_NAME WHERE $COLUMN_PACKAGE = ?",
arrayOf(packageName)
).use { cursor ->
if (cursor.moveToFirst()) {
val showProgress = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_SHOW_PROGRESS))
val colorIndex = cursor.getColumnIndexOrThrow(COLUMN_COLOR)
val color = if (cursor.isNull(colorIndex)) {
null
} else {
cursor.getInt(colorIndex)
}
val useDefault = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_USE_DEFAULT))
val useMaterialYou = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_USE_MATERIAL_YOU))
return ProgressBarApp(
packageName,
showProgress == 1,
color,
useDefault == 1,
useMaterialYou == 1
)
}
}
return null
}
private fun getAllApps(): MutableList<ProgressBarApp> {
val apps = mutableListOf<ProgressBarApp>()
readableDatabase.rawQuery(
"SELECT * FROM $TABLE_NAME", null
).use { cursor ->
val packageIndex = cursor.getColumnIndexOrThrow(COLUMN_PACKAGE)
val showProgressIndex = cursor.getColumnIndexOrThrow(COLUMN_SHOW_PROGRESS)
val colorIndex = cursor.getColumnIndexOrThrow(COLUMN_COLOR)
val useDefaultIndex = cursor.getColumnIndexOrThrow(COLUMN_USE_DEFAULT)
val useMaterialYouIndex = cursor.getColumnIndexOrThrow(COLUMN_USE_MATERIAL_YOU)
while (cursor.moveToNext()) {
val packageName = cursor.getString(packageIndex)
val showProgress = cursor.getInt(showProgressIndex)
val color = if (cursor.isNull(colorIndex)) {
null
} else {
cursor.getInt(colorIndex)
}
val useDefault = cursor.getInt(useDefaultIndex)
val useMaterialYou = cursor.getInt(useMaterialYouIndex)
apps.add(
ProgressBarApp(
packageName,
showProgress == 1,
color,
useDefault == 1,
useMaterialYou == 1
)
)
}
}
return apps
}
fun getAll(): List<ProgressBarApp> {
return apps.toList()
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/SettingsActivity.kt
================================================
package com.gustavoas.noti
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Xml
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
import com.gustavoas.noti.Utils.dpToPx
import com.gustavoas.noti.Utils.getFirebaseConfigStorageReference
import com.gustavoas.noti.Utils.getScreenLargeSide
import com.gustavoas.noti.Utils.getScreenSmallSide
import com.gustavoas.noti.Utils.hasAccessibilityPermission
import com.gustavoas.noti.Utils.hasNotificationListenerPermission
import com.gustavoas.noti.Utils.hasSystemAlertWindowPermission
import com.gustavoas.noti.fragments.CircularBarFragment
import com.gustavoas.noti.fragments.LinearBarFragment
import com.gustavoas.noti.fragments.PerAppSettingsFragment
import com.gustavoas.noti.fragments.SettingsFragment
import com.gustavoas.noti.model.DeviceConfiguration
import com.gustavoas.noti.model.ProgressBarApp
import com.gustavoas.noti.model.ProgressNotification
import com.gustavoas.noti.services.AccessibilityService
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.tasks.await
import org.xmlpull.v1.XmlPullParser
import java.io.InputStream
import kotlin.math.abs
import kotlin.math.roundToInt
import kotlin.math.sqrt
class SettingsActivity : AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback,
SharedPreferences.OnSharedPreferenceChangeListener {
private val previewFab by lazy { findViewById<ExtendedFloatingActionButton>(R.id.previewFab) }
private val topAppBar by lazy { findViewById<MaterialToolbar>(R.id.topAppBar) }
private val appBarLayout by lazy { findViewById<AppBarLayout>(R.id.appBarLayout) }
private var offsetChangeListener: OnOffsetChangedListener? = null
private val handler = Handler(Looper.getMainLooper())
private val sizeDependentPrefs = arrayOf(
Pair("advancedProgressBarStyle", false),
Pair("progressBarStyle", "linear"),
Pair("progressBarStylePortrait", "linear"),
Pair("progressBarStyleLandscape", "linear"),
Pair("circularProgressBarThickness", 15),
Pair("circularProgressBarSize", 65),
Pair("circularProgressBarTopOffset", 60),
Pair("circularProgressBarHorizontalOffset", 0),
Pair("linearProgressBarSize", 15),
Pair("matchStatusBarHeight", false),
Pair("linearProgressBarMarginTop", 0),
Pair("showBelowNotch", false),
)
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (sizeDependentPrefs.any { it.first == key }) {
val defaultValue = sizeDependentPrefs.first { it.first == key }.second
val width = getScreenSmallSide(this)
val height = getScreenLargeSide(this)
moveSharedPreferenceValue(key + height + "x" + width, key!!, defaultValue)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
setTheme(R.style.SplashScreen)
installSplashScreen()
} else {
setTheme(R.style.Theme_NotiProgressBar)
}
super.onCreate(savedInstanceState)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
if (!sharedPreferences.contains("progressBarStyle")) {
runBlocking {
setupDeviceConfiguration()
}
}
if (!sharedPreferences.contains("progressBarColor")) {
setMaterialYouAsDefault()
}
setupSizeDependentPrefs()
sharedPreferences
.registerOnSharedPreferenceChangeListener(this)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commitNow()
}
updateUpNavigationVisibility()
ViewCompat.setOnApplyWindowInsetsListener(window.decorView.rootView) { _, insets ->
val keyboardInset = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
if (keyboardInset > 0) {
appBarLayout.setExpanded(false, true)
}
insets
}
}
private fun setupSizeDependentPrefs() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val width = getScreenSmallSide(this)
val height = getScreenLargeSide(this)
for (pref in sizeDependentPrefs) {
val relatedPrefs = sharedPreferences.all.filterKeys { it.startsWith(pref.first) }
val sameRatio = relatedPrefs.filterKeys { it.length > pref.first.length && it[pref.first.length].isDigit() && it.drop(pref.first.length).split("x")[0].toInt() / it.drop(pref.first.length).split("x")[1].toInt() == height / width }
if (sameRatio.isEmpty()) {
continue
}
val closest = sameRatio.keys.minByOrNull { abs(it.drop(pref.first.length).split("x")[0].toInt() * it.drop(pref.first.length).split("x")[1].toInt() - height * width) }
var reductionRatio = 1f
if (pref.second is Int) {
reductionRatio = sqrt((height * width).div(closest!!.drop(pref.first.length).split("x")[0].toFloat() * closest.drop(pref.first.length).split("x")[1].toFloat()))
}
moveSharedPreferenceValue(pref.first, closest!!, pref.second, reductionRatio)
}
}
private fun moveSharedPreferenceValue(key: String, oldKey: String, defaultValue: Any, reductionRatio: Float = 1f) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences.edit {
when (defaultValue) {
is Boolean -> putBoolean(key, sharedPreferences.getBoolean(oldKey, defaultValue))
is Int -> putInt(
key,
sharedPreferences.getInt(oldKey, defaultValue).times(reductionRatio)
.roundToInt()
)
is String -> putString(key, sharedPreferences.getString(oldKey, defaultValue))
}
}
}
override fun onStart() {
super.onStart()
val collapsingToolbarLayout = findViewById<CollapsingToolbarLayout>(R.id.collapsingToolbar)
var isVisible = true
var scrollRange = -1
offsetChangeListener =
OnOffsetChangedListener { barLayout, verticalOffset ->
if (scrollRange == -1) {
scrollRange = barLayout?.totalScrollRange!!
}
if (scrollRange + verticalOffset < dpToPx(this, 25)) {
collapsingToolbarLayout.title = resources.getString(R.string.app_name_short)
isVisible = true
} else if (isVisible) {
collapsingToolbarLayout.title = resources.getString(R.string.app_name)
isVisible = false
}
}
appBarLayout.addOnOffsetChangedListener(offsetChangeListener)
if (hasNotificationListenerPermission(this) && (hasAccessibilityPermission(this) || hasSystemAlertWindowPermission(this))) {
previewFab.visibility = View.VISIBLE
} else {
previewFab.visibility = View.GONE
}
previewFab.setOnClickListener {
simulateDownload()
}
supportFragmentManager.addOnBackStackChangedListener {
updateUpNavigationVisibility()
}
topAppBar.setNavigationOnClickListener {
supportFragmentManager.popBackStack()
}
topAppBar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.bug_report -> {
val sendEmail = Intent(Intent.ACTION_SENDTO).apply {
data =
("mailto:gustavoasgas1+noti@gmail.com" + "?subject=" + Uri.encode("Noti")).toUri()
}
sendEmail.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(sendEmail)
true
}
else -> false
}
}
}
override fun onStop() {
super.onStop()
if (offsetChangeListener != null) {
appBarLayout.removeOnOffsetChangedListener(offsetChangeListener!!)
}
}
override fun onDestroy() {
super.onDestroy()
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this)
}
override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
val fragment = when (pref.key) {
"CircularBarFragment" -> CircularBarFragment()
"LinearBarFragment" -> LinearBarFragment()
"PerAppSettingsFragment" -> PerAppSettingsFragment()
else -> null
}
supportFragmentManager.beginTransaction()
.replace(R.id.settings, fragment ?: return false)
.addToBackStack(null)
.commit()
return true
}
private fun simulateDownload() {
val intent = Intent(this, AccessibilityService::class.java)
handler.removeCallbacksAndMessages(null)
val maxProgress = this.resources.getInteger(R.integer.progress_bar_max)
val numberOfSteps = 4
val stepSize = maxProgress / numberOfSteps
for (i in stepSize..(maxProgress + stepSize) step stepSize) {
handler.postDelayed({
intent.putExtra("removal", i > maxProgress)
intent.putExtra("id", packageName)
intent.putExtra(
"progressNotification",
ProgressNotification(
ProgressBarApp(
packageName,
true
),
i,
10
)
)
startService(intent)
}, ((i - stepSize) * 1000 / stepSize).toLong())
}
}
private suspend fun setupDeviceConfiguration() {
if (!Utils.isInternetAvailable(this)) {
return
}
val configRef = getFirebaseConfigStorageReference()
try {
val taskSnapshot = configRef.stream.await()
val inputStream: InputStream = taskSnapshot.stream
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences.edit {
putString("progressBarStyle", "circular")
}
parseDeviceConfiguration(inputStream)
} catch (_: Exception) {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
sharedPreferences.edit {
putString("progressBarStyle", "linear")
}
}
}
private fun parseDeviceConfiguration(input: InputStream) {
val parser: XmlPullParser = Xml.newPullParser()
parser.setInput(input, null)
while (parser.eventType != XmlPullParser.END_DOCUMENT) {
when (parser.eventType) {
XmlPullParser.START_TAG -> {
if (parser.name == "display") {
val displayConfig = DeviceConfiguration()
displayConfig.deviceWidth = parser.getAttributeValue(null, "width")
displayConfig.deviceHeight = parser.getAttributeValue(null, "height")
displayConfig.configuration = parser.getAttributeValue(null, "configuration")
parseDisplay(displayConfig, parser)
}
}
}
parser.next()
}
}
private fun parseDisplay(config: DeviceConfiguration, parser: XmlPullParser) {
while (parser.eventType != XmlPullParser.END_DOCUMENT) {
when (parser.eventType) {
XmlPullParser.START_TAG -> {
when (parser.name) {
"size" -> config.size = parser.nextText()
"marginTop" -> config.marginTop = parser.nextText()
"topOffset" -> config.topOffset = parser.nextText()
"offset" -> config.horizontalOffset = parser.nextText()
}
}
XmlPullParser.END_TAG -> {
if (parser.name == "display") {
break
}
}
}
parser.next()
}
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val currentWidth = getScreenSmallSide(this)
val currentHeight = getScreenLargeSide(this)
if (config.deviceWidth == "" || config.deviceHeight == "") {
if ((config.deviceWidth != "" && currentWidth == config.deviceWidth?.toInt()) ||
(config.deviceHeight != "" && currentHeight == config.deviceHeight?.toInt())) {
config.deviceWidth = currentWidth.toString()
config.deviceHeight = currentHeight.toString()
}
if (config.deviceWidth == "" && config.deviceHeight != "") {
config.deviceWidth = (currentWidth * config.deviceHeight!!.toInt() / currentHeight).toString()
} else if (config.deviceHeight == "" && config.deviceWidth != "") {
config.deviceHeight = (currentHeight * config.deviceWidth!!.toInt() / currentWidth).toString()
}
if (config.deviceWidth == "" || config.deviceHeight == "") {
config.deviceWidth = currentWidth.toString()
config.deviceHeight = currentHeight.toString()
}
}
val appendix = config.deviceHeight + "x" + config.deviceWidth
if (config.configuration == "circular") {
sharedPreferences.edit {
putString("progressBarStyle$appendix", "circular")
putBoolean("blackBackground", true)
putInt("circularProgressBarSize$appendix", config.size?.toIntOrNull() ?: 65)
putInt("circularProgressBarTopOffset$appendix", config.topOffset?.toIntOrNull() ?: 60)
putInt("circularProgressBarHorizontalOffset$appendix", config.horizontalOffset?.toIntOrNull() ?: 0)
}
} else {
sharedPreferences.edit {
putString("progressBarStyle$appendix", "linear")
}
}
}
private fun setMaterialYouAsDefault() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
sharedPreferences.edit {
putInt(
"progressBarColor", ContextCompat.getColor(
this@SettingsActivity,
R.color.system_accent_color
)
)
putBoolean("usingMaterialYouColor", true)
}
} else {
sharedPreferences.edit {
putInt(
"progressBarColor", ContextCompat.getColor(
this@SettingsActivity,
R.color.purple_500
)
)
}
}
}
private fun updateUpNavigationVisibility() {
if (supportFragmentManager.backStackEntryCount > 0) {
topAppBar.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material)
topAppBar.setNavigationIconTint(ContextCompat.getColor(this, R.color.text))
topAppBar.setTitleMargin(0, 0, dpToPx(this, 40), 0)
} else {
topAppBar.navigationIcon = null
topAppBar.setTitleMargin(0, 0, 0, 0)
}
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/Utils.kt
================================================
package com.gustavoas.noti
import android.content.ComponentName
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.drawable.Drawable
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.os.Build
import android.os.VibrationEffect
import android.os.Vibrator
import android.os.VibratorManager
import android.provider.Settings
import android.util.DisplayMetrics
import android.view.Display
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import com.google.firebase.ktx.Firebase
import com.google.firebase.storage.StorageReference
import com.google.firebase.storage.ktx.storage
import com.gustavoas.noti.model.ProgressBarApp
import com.gustavoas.noti.services.AccessibilityService
import com.gustavoas.noti.services.NotificationListenerService
import eltos.simpledialogfragment.color.SimpleColorDialog
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
import java.nio.charset.StandardCharsets
object Utils {
fun hasAccessibilityPermission(context: Context): Boolean {
val accessibilityServiceComponentName = ComponentName(context, AccessibilityService::class.java)
val enabledServices = Settings.Secure.getString(context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)
return enabledServices.orEmpty().let {
it.contains(accessibilityServiceComponentName.flattenToString()) ||
it.contains(accessibilityServiceComponentName.flattenToShortString())
}
}
fun hasNotificationListenerPermission(context: Context): Boolean {
val notificationListenerComponentName = ComponentName(context, NotificationListenerService::class.java)
val enabledServices = Settings.Secure.getString(context.contentResolver, "enabled_notification_listeners")
return enabledServices?.contains(notificationListenerComponentName.flattenToString()) ?: false
}
fun hasSystemAlertWindowPermission(context: Context): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Settings.canDrawOverlays(context)
} else {
true
}
}
fun dpToPx(context: Context, dp: Int): Int {
return (dp * context.resources.displayMetrics.density).toInt()
}
fun isInternetAvailable(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork ?: return false
val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
} else {
val activeNetworkInfo = connectivityManager.activeNetworkInfo
return activeNetworkInfo != null && activeNetworkInfo.isConnected
}
}
fun getFirebaseConfigStorageReference(): StorageReference {
val storage = Firebase.storage
val storageRef = storage.reference
val brand = Build.BRAND.lowercase()
var model = Build.DEVICE.lowercase().replace("\\W".toRegex(), "")
if (brand == "xiaomi" || brand == "redmi" || brand == "poco") {
while (model.endsWith("in")) {
model = model.dropLast(2)
}
}
return storageRef.child("configs/$brand/$model.xml")
}
fun shareConfigToFirebase(context: Context) {
if (!isInternetAvailable(context)) {
Toast.makeText(context, context.getString(R.string.shareConfigNoInternetMessage), Toast.LENGTH_SHORT).show()
return
}
val configRef = getFirebaseConfigStorageReference()
configRef.stream.addOnSuccessListener { taskSnapshot ->
val inputStream: InputStream = taskSnapshot.stream
val outputStream = ByteArrayOutputStream()
val buffer = ByteArray(1024)
var length: Int
while (inputStream.read(buffer).also { length = it } != -1) {
outputStream.write(buffer, 0, length)
}
val existingFileContent = outputStream.toString(StandardCharsets.UTF_8.name())
if (existingFileContent != buildConfig(context)) {
uploadConfigToStorageRef(context, configRef)
}
}.addOnFailureListener {
uploadConfigToStorageRef(context, configRef)
}
Toast.makeText(context, context.getString(R.string.shareConfigPositiveMessage), Toast.LENGTH_SHORT).show()
}
private fun uploadConfigToStorageRef(context: Context, storageRef: StorageReference) {
val config = buildConfig(context)
val xmlInputStream = ByteArrayInputStream(config.toByteArray(StandardCharsets.UTF_8))
storageRef.putStream(xmlInputStream)
}
private fun buildConfig(context: Context): String {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val stylePrefs = sharedPreferences.all.filterKeys { it.startsWith("progressBarStyle") && it.last().isDigit() }
if (stylePrefs.isEmpty() || stylePrefs.none { it.value == "circular" }) {
return ""
}
val config = StringBuilder()
config.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n")
config.append("<device>\n")
val sizes = stylePrefs.keys.map { str -> str.dropWhile { !it.isDigit() } }.distinct()
sizes.forEach {
config.append(buildConfigForDisplay(context, it)).append("\n")
}
config.append("</device>")
return config.toString()
}
private fun buildConfigForDisplay(context: Context, display: String): String {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
val height = display.split("x")[0].toInt()
val width = display.split("x")[1].toInt()
val configuration = sharedPreferences.getString("progressBarStyle$display", "linear")
val displayConfig = StringBuilder()
displayConfig.append("\t<display width=\"$width\" height=\"$height\" configuration=\"$configuration\">\n")
if (configuration == "circular") {
val size = sharedPreferences.getInt("circularProgressBarSize$display", 65)
val marginTop = sharedPreferences.getInt("circularProgressBarTopOffset$display", 60)
val offset = sharedPreferences.getInt("circularProgressBarHorizontalOffset$display", 0)
displayConfig.append("\t\t<size>$size</size>\n")
displayConfig.append("\t\t<topOffset>$marginTop</topOffset>\n")
displayConfig.append("\t\t<offset>$offset</offset>\n")
}
displayConfig.append("\t</display>")
return displayConfig.toString()
}
fun showColorDialog(fragment: Fragment, color: Int, tag: String, reset: Boolean = false) {
if (reset) {
SimpleColorDialog.build()
.colorPreset(color)
.colors(fragment.context, R.array.colorsArrayValues)
.allowCustom(true)
.showOutline(0x46000000)
.gridNumColumn(5)
.choiceMode(SimpleColorDialog.SINGLE_CHOICE)
.neg()
.neut(R.string.reset)
.show(fragment, tag)
} else {
SimpleColorDialog.build()
.colorPreset(color)
.colors(fragment.context, R.array.colorsArrayValues)
.allowCustom(true)
.showOutline(0x46000000)
.gridNumColumn(5)
.choiceMode(SimpleColorDialog.SINGLE_CHOICE)
.neg()
.show(fragment, tag)
}
}
fun getRealDisplayHeight(context: Context): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as android.view.WindowManager
windowManager.currentWindowMetrics.bounds.height()
} else {
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as android.hardware.display.DisplayManager
val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)!!
val metrics = DisplayMetrics()
display.getRealMetrics(metrics)
metrics.heightPixels
}
}
private fun getRealDisplayWidth(context: Context): Int {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as android.view.WindowManager
windowManager.currentWindowMetrics.bounds.width()
} else {
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as android.hardware.display.DisplayManager
val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)!!
val metrics = DisplayMetrics()
display.getRealMetrics(metrics)
metrics.widthPixels
}
}
fun getScreenSmallSide(context: Context): Int {
return minOf(getRealDisplayHeight(context), getRealDisplayWidth(context))
}
fun getScreenLargeSide(context: Context): Int {
return maxOf(getRealDisplayHeight(context), getRealDisplayWidth(context))
}
fun vibrate(context: Context) {
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val vibratorManager =
ContextCompat.getSystemService(context, VibratorManager::class.java)
vibratorManager?.defaultVibrator
} else {
@Suppress("DEPRECATION")
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
}
if (vibrator == null) {
return
}
vibrator.cancel()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
vibrator.vibrate(VibrationEffect.createPredefined(VibrationEffect.EFFECT_TICK))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
vibrator.vibrate(VibrationEffect.createOneShot(1, 200))
} else {
@Suppress("DEPRECATION")
vibrator.vibrate(1)
}
}
fun getStatusBarHeight(context: Context): Int {
return context.resources.getDimensionPixelSize(
(context.resources.getIdentifier(
"status_bar_height",
"dimen",
"android"
)))
}
fun getApplicationInfo(context: Context, packageName: String): ApplicationInfo? {
return try {
context.packageManager.getApplicationInfo(packageName, 0)
} catch (e: NameNotFoundException) {
null
}
}
fun getApplicationName(context: Context, packageName: String): String? {
return context.packageManager.getApplicationLabel(
getApplicationInfo(context, packageName) ?: return null
).toString()
}
fun getApplicationIcon(context: Context, packageName: String): Drawable? {
return context.packageManager.getApplicationIcon(
getApplicationInfo(context, packageName) ?: return null
)
}
fun getColorForApp(context: Context, progressBarApp: ProgressBarApp): Int {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context)
val useNotificationColor = sharedPrefs.getBoolean("useNotificationColor", false)
val useMaterialYou = sharedPrefs.getBoolean("usingMaterialYouColor", false)
if ((progressBarApp.useDefaultColor && useNotificationColor) ||
(!progressBarApp.useDefaultColor && !progressBarApp.useMaterialYouColor)) {
progressBarApp.color?.let { return it }
}
if ((progressBarApp.useMaterialYouColor) ||
(progressBarApp.useDefaultColor && useMaterialYou)) {
return ContextCompat.getColor(context, R.color.system_accent_color)
}
return sharedPrefs.getInt(
"progressBarColor", ContextCompat.getColor(context, R.color.purple_500)
)
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/fragments/BasePreferenceFragment.kt
================================================
package com.gustavoas.noti.fragments
import android.os.Bundle
import android.view.View
import androidx.preference.PreferenceFragmentCompat
import com.gustavoas.noti.Utils
abstract class BasePreferenceFragment : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val preferencesView = listView
preferencesView.setPadding(0, 0, 0, Utils.dpToPx(requireContext(), 100))
preferencesView.isVerticalScrollBarEnabled = false
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/fragments/CircularBarFragment.kt
================================================
package com.gustavoas.noti.fragments
import android.content.SharedPreferences
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.gustavoas.noti.R
import com.gustavoas.noti.Utils
class CircularBarFragment : BasePreferenceFragment(),
SharedPreferences.OnSharedPreferenceChangeListener {
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
// TODO: If offset is not zero and hole punch size changes toast suggesting a 0.5x change
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.circular_bar_preferences, rootKey)
findPreference<Preference>("shareConfig")?.setOnPreferenceClickListener {
Utils.shareConfigToFirebase(requireContext())
true
}
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override fun onDestroy() {
super.onDestroy()
PreferenceManager.getDefaultSharedPreferences(requireContext())
.unregisterOnSharedPreferenceChangeListener(this)
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/fragments/LinearBarFragment.kt
================================================
package com.gustavoas.noti.fragments
import android.content.SharedPreferences
import android.os.Build
import android.os.Bundle
import androidx.preference.Preference
import androidx.preference.PreferenceManager
import com.gustavoas.noti.R
import com.gustavoas.noti.Utils.getStatusBarHeight
class LinearBarFragment : BasePreferenceFragment(),
SharedPreferences.OnSharedPreferenceChangeListener {
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == "linearProgressBarSize") {
val size = sharedPreferences?.getInt(key, 15) ?: 15
sharedPreferences?.edit()
?.putBoolean("matchStatusBarHeight", size == getStatusBarHeight(context ?: return))
?.apply()
}
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.linear_bar_preferences, rootKey)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
findPreference<Preference>("statusBarHeightCard")?.summary =
getString(R.string.prefsStatusBarHeightInfo, getStatusBarHeight(requireContext()))
if (sharedPreferences.getBoolean("matchStatusBarHeight", false)) {
sharedPreferences.edit()
.putInt("linearProgressBarSize", getStatusBarHeight(requireContext())).apply()
}
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P || !hasDisplayCutout()) {
findPreference<Preference>("showBelowNotch")?.isVisible = false
}
}
}
private fun hasDisplayCutout(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val windowInsets = requireActivity().window?.decorView?.rootWindowInsets
val displayCutout = windowInsets?.displayCutout
displayCutout != null
} else {
false
}
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/fragments/PerAppSettingsFragment.kt
================================================
package com.gustavoas.noti.fragments
import android.os.Build
import android.os.Build.VERSION_CODES
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gustavoas.noti.ProgressBarAppsAdapter
import com.gustavoas.noti.ProgressBarAppsRepository
import com.gustavoas.noti.R
import com.gustavoas.noti.Utils.getApplicationInfo
import com.gustavoas.noti.Utils.getApplicationName
import com.gustavoas.noti.Utils.getColorForApp
import com.gustavoas.noti.model.ProgressBarApp
import eltos.simpledialogfragment.SimpleDialog
import eltos.simpledialogfragment.SimpleDialog.OnDialogResultListener.BUTTON_NEUTRAL
import eltos.simpledialogfragment.SimpleDialog.OnDialogResultListener.BUTTON_POSITIVE
import eltos.simpledialogfragment.color.SimpleColorDialog
class PerAppSettingsFragment : Fragment(), SimpleDialog.OnDialogResultListener {
private val apps = ArrayList<ProgressBarApp>()
private val appsRepository by lazy { ProgressBarAppsRepository(requireContext()) }
private val recyclerView by lazy { requireView().findViewById<RecyclerView>(R.id.apps_recycler_view) }
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.fragment_per_app_settings, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
updateAppsFromDatabase()
recyclerView.layoutManager = LinearLayoutManager(requireContext())
recyclerView.adapter =
ProgressBarAppsAdapter(this, requireContext(), apps, appsRepository)
updateRecyclerViewVisibility()
}
override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean {
val progressBarApp = apps[dialogTag.toInt()].copy()
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(requireContext())
val defaultColor = sharedPrefs.getInt(
"progressBarColor", ContextCompat.getColor(requireContext(), R.color.purple_500)
)
val existingSelectedColor = getColorForApp(requireContext(), progressBarApp)
val useMaterialYou = sharedPrefs.getBoolean("usingMaterialYouColor", false)
val color = extras.getInt(SimpleColorDialog.COLOR)
val selectedPosition = extras.getInt(SimpleColorDialog.SELECTED_SINGLE_POSITION)
if (which == BUTTON_NEUTRAL || (color == defaultColor && (!useMaterialYou || selectedPosition != 19))) {
progressBarApp.useDefaultColor = true
progressBarApp.useMaterialYouColor = false
progressBarApp.color = null
} else if (which == BUTTON_POSITIVE) {
if (
Build.VERSION.SDK_INT >= VERSION_CODES.S && selectedPosition != 19 &&
color == ContextCompat.getColor(requireContext(), R.color.system_accent_color)
) {
progressBarApp.useDefaultColor = false
progressBarApp.useMaterialYouColor = true
progressBarApp.color = null
} else if (color != existingSelectedColor || (progressBarApp.useMaterialYouColor && selectedPosition == 19)) {
progressBarApp.useDefaultColor = false
progressBarApp.useMaterialYouColor = false
progressBarApp.color = color
}
}
if (apps[dialogTag.toInt()] != progressBarApp) {
apps[dialogTag.toInt()] = progressBarApp
appsRepository.updateApp(apps[dialogTag.toInt()])
recyclerView.adapter?.notifyItemChanged(dialogTag.toInt() + 1)
}
return true
}
private fun updateAppsFromDatabase() {
apps.clear()
apps.addAll(appsRepository.getAll())
removeUnavailableApps()
alphabetizeApps()
}
private fun removeUnavailableApps() {
apps.removeAll { app ->
getApplicationInfo(requireContext(), app.packageName)?.enabled != true
}
}
private fun alphabetizeApps() {
apps.sortBy { app ->
(getApplicationName(requireContext(), app.packageName) ?: app.packageName).lowercase()
}
}
private fun updateRecyclerViewVisibility() {
val emptyRecyclerView = requireView().findViewById<TextView>(R.id.empty_view)
if (apps.isEmpty()) {
recyclerView.visibility = View.GONE
emptyRecyclerView.visibility = View.VISIBLE
} else {
recyclerView.visibility = View.VISIBLE
emptyRecyclerView.visibility = View.GONE
}
}
override fun onDestroy() {
super.onDestroy()
appsRepository.close()
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/fragments/SettingsFragment.kt
================================================
package com.gustavoas.noti.fragments
import android.content.ComponentName
import android.content.Intent
import android.content.SharedPreferences
import android.content.res.Configuration.ORIENTATION_LANDSCAPE
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceManager
import androidx.preference.children
import com.gustavoas.noti.R
import com.gustavoas.noti.Utils.hasAccessibilityPermission
import com.gustavoas.noti.Utils.hasNotificationListenerPermission
import com.gustavoas.noti.Utils.hasSystemAlertWindowPermission
import com.gustavoas.noti.Utils.showColorDialog
import com.gustavoas.noti.preferences.BannerPreference
import com.gustavoas.noti.services.NotificationListenerService
import com.kizitonwose.colorpreferencecompat.ColorPreferenceCompat
import eltos.simpledialogfragment.SimpleDialog
import eltos.simpledialogfragment.SimpleDialog.OnDialogResultListener.BUTTON_POSITIVE
import eltos.simpledialogfragment.color.SimpleColorDialog
class SettingsFragment : BasePreferenceFragment(),
SharedPreferences.OnSharedPreferenceChangeListener, SimpleDialog.OnDialogResultListener {
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
if (key == "progressBarStyle" || key == "progressBarStylePortrait" || key == "progressBarStyleLandscape" || key == "advancedProgressBarStyle") {
if (key != "advancedProgressBarStyle" && sharedPreferences?.getString(
key, "linear"
) == "circular" && sharedPreferences.getBoolean("showHolePunchInstruction", true)
) {
Toast.makeText(
requireContext(), getString(R.string.holePunchInstruction), Toast.LENGTH_LONG
).show()
sharedPreferences.edit { putBoolean("showHolePunchInstruction", false) }
}
updateProgressBarStyle()
} else if (key == "progressBarColor") {
updateColorPreferenceSummary()
} else if (key == "useNotificationColor") {
updateColorPreferenceState()
}
}
private val myApps = listOf(
Triple("CalenTile", "https://play.google.com/store/apps/details?id=com.gustavoas.calendarqst", R.drawable.ic_calentile),
Triple("Sum Up", "https://play.google.com/store/apps/details?id=com.gustavoas.sumup", R.drawable.ic_sumup),
)
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
updateSetupVisibility()
updateProgressBarStyle()
updatePermissionDependentPreferences()
updateColorPreferenceState()
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
sharedPreferences.registerOnSharedPreferenceChangeListener(this)
val appIndex = myApps.indices.random()
val appPromo = findPreference<Preference>("selfPromo")
appPromo?.title = getString(R.string.prefsCalentileTitle, myApps[appIndex].first)
appPromo?.intent = Intent(Intent.ACTION_VIEW, myApps[appIndex].second.toUri())
appPromo?.icon = ContextCompat.getDrawable(requireContext(), myApps[appIndex].third)
if (!sharedPreferences.contains("batteryOptimizations")) {
sharedPreferences.edit {
putBoolean(
"batteryOptimizations",
Build.BRAND.lowercase() != "google"
)
}
}
val batterOptimizationsBanner = findPreference<BannerPreference>("batteryOptimizations")
if (!sharedPreferences.getBoolean("batteryOptimizations", true)) {
batterOptimizationsBanner?.isVisible = false
}
findPreference<Preference>("accessibilityPermission")?.setOnPreferenceClickListener {
showAccessibilityDialog()
true
}
findPreference<Preference>("progressBarColor")?.setOnPreferenceClickListener {
val color = PreferenceManager.getDefaultSharedPreferences(requireContext()).getInt(
"progressBarColor", ContextCompat.getColor(requireContext(), R.color.purple_500)
)
showColorDialog(this, color, "colorPicker")
true
}
findPreference<Preference>("notificationPermission")?.setOnPreferenceClickListener {
requestNotificationAccess()
true
}
}
override fun onStart() {
super.onStart()
updateSetupVisibility()
updateColorPreferenceSummary()
updatePermissionDependentPreferences()
}
override fun onDestroy() {
super.onDestroy()
PreferenceManager.getDefaultSharedPreferences(requireContext())
.unregisterOnSharedPreferenceChangeListener(this)
}
override fun onResult(dialogTag: String, which: Int, extras: Bundle): Boolean {
if (which == BUTTON_POSITIVE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
PreferenceManager.getDefaultSharedPreferences(requireContext()).edit {
putBoolean(
"usingMaterialYouColor",
extras.getInt(SimpleColorDialog.COLOR) == ContextCompat.getColor(
requireContext(), R.color.system_accent_color
) && extras.getInt(SimpleColorDialog.SELECTED_SINGLE_POSITION) != 19
)
}
}
findPreference<ColorPreferenceCompat>("progressBarColor")?.value = extras.getInt(
SimpleColorDialog.COLOR,
ContextCompat.getColor(requireContext(), R.color.purple_500)
)
}
return true
}
private fun updateColorPreferenceState() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val colorPreference = findPreference<ColorPreferenceCompat>("progressBarColor")
val useNotificationColor = sharedPreferences.getBoolean("useNotificationColor", false)
colorPreference?.isEnabled = !useNotificationColor
colorPreference?.icon?.alpha = if (useNotificationColor) 80 else 255
}
private fun updateColorPreferenceSummary() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val color = sharedPreferences.getInt(
"progressBarColor", ContextCompat.getColor(requireContext(), R.color.purple_500)
)
val colorPosition = resources.getIntArray(R.array.colorsArrayValues).indexOf(color)
var colorName = resources.getStringArray(R.array.colorsArray).getOrNull(colorPosition)
val useMaterialYou = sharedPreferences.getBoolean("usingMaterialYouColor", false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && colorName == null && useMaterialYou) {
sharedPreferences.edit {
putInt(
"progressBarColor", ContextCompat.getColor(
requireContext(), R.color.system_accent_color
)
)
}
findPreference<ColorPreferenceCompat>("progressBarColor")?.value =
ContextCompat.getColor(requireContext(), R.color.system_accent_color)
colorName = resources.getString(R.string.colorMaterialYou)
}
findPreference<Preference>("progressBarColor")?.summary =
colorName ?: "#${Integer.toHexString(color).drop(2).uppercase()}"
}
private fun updateProgressBarStyle() {
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(requireContext())
val advancedStyleOptions = sharedPreferences.getBoolean("advancedProgressBarStyle", false)
val progressBarStyle = sharedPreferences.getString("progressBarStyle", "linear")
val progressBarStylePortrait =
sharedPreferences.getString("progressBarStylePortrait", "linear")
val progressBarStyleLandscape =
sharedPreferences.getString("progressBarStyleLandscape", "linear")
val anyLinear =
(!advancedStyleOptions && progressBarStyle == "linear") || (advancedStyleOptions && (progressBarStylePortrait == "linear" || progressBarStyleLandscape == "linear"))
val anyCircular =
(!advancedStyleOptions && progressBarStyle == "circular") || (advancedStyleOptions && (progressBarStylePortrait == "circular" || progressBarStyleLandscape == "circular"))
findPreference<Preference>("CircularBarFragment")?.isVisible = anyCircular
findPreference<Preference>("LinearBarFragment")?.isVisible = anyLinear
findPreference<Preference>("progressBarStyle")?.summary = if (advancedStyleOptions) {
if (resources.configuration.orientation == ORIENTATION_LANDSCAPE) {
resources.getStringArray(R.array.progressBarStyle).getOrNull(
resources.getStringArray(R.array.progressBarStyleValues)
.indexOf(progressBarStyleLandscape)
)
} else {
resources.getStringArray(R.array.progressBarStyle).getOrNull(
resources.getStringArray(R.array.progressBarStyleValues)
.indexOf(progressBarStylePortrait)
)
}
} else {
resources.getStringArray(R.array.progressBarStyle).getOrNull(
resources.getStringArray(R.array.progressBarStyleValues).indexOf(progressBarStyle)
)
}
}
private fun updatePermissionDependentPreferences() {
findPreference<Preference>("showInLockScreen")?.isVisible =
(hasAccessibilityPermission(requireContext()) || Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
findPreference<Preference>("disableInFullScreen")?.isVisible =
hasSystemAlertWindowPermission(requireContext())
}
private fun updateSetupVisibility() {
val hasAccessibilityPermission = hasAccessibilityPermission(requireContext())
val hasNotificationListenerPermission = hasNotificationListenerPermission(requireContext())
val hasSystemAlertWindowPermission = hasSystemAlertWindowPermission(requireContext())
findPreference<Preference>("accessibilityPermission")?.isVisible =
!hasAccessibilityPermission
findPreference<Preference>("notificationPermission")?.isVisible =
!hasNotificationListenerPermission
findPreference<Preference>("systemAlertWindowPermission")?.isVisible =
!hasSystemAlertWindowPermission
findPreference<PreferenceCategory>("setup")?.isVisible = findPreference<PreferenceCategory>("setup")?.children?.any { it.isVisible } == true
}
private fun showAccessibilityDialog() {
AlertDialog.Builder(requireContext())
.setTitle(R.string.prefsAccessibilityPermissionTitle)
.setMessage(R.string.prefsAccessibilityPermissionSummary)
.setPositiveButton(android.R.string.ok) { _, _ ->
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun requestNotificationAccess() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val intent = Intent(Settings.ACTION_NOTIFICATION_LISTENER_DETAIL_SETTINGS)
intent.putExtra(
Settings.EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME, ComponentName(
requireContext(), NotificationListenerService::class.java
).flattenToString()
)
try {
startActivity(intent)
} catch (e: Exception) {
startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
}
} else {
startActivity(Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"))
}
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/model/DeviceConfiguration.kt
================================================
package com.gustavoas.noti.model
data class DeviceConfiguration (
var configuration: String? = null,
var deviceWidth: String? = null,
var deviceHeight: String? = null,
var size: String? = null,
var marginTop: String? = null,
var topOffset: String? = null,
var horizontalOffset: String? = null,
)
================================================
FILE: app/src/main/java/com/gustavoas/noti/model/ProgressBarApp.kt
================================================
package com.gustavoas.noti.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class ProgressBarApp(
val packageName: String,
var showProgressBar: Boolean = true,
var color: Int? = null,
var useDefaultColor: Boolean = true,
var useMaterialYouColor: Boolean = false
) : Parcelable
================================================
FILE: app/src/main/java/com/gustavoas/noti/model/ProgressNotification.kt
================================================
package com.gustavoas.noti.model
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
data class ProgressNotification (
val progressBarApp: ProgressBarApp,
val progress: Int,
val priority: Int
) : Parcelable
================================================
FILE: app/src/main/java/com/gustavoas/noti/notifications/DownloadProgressBar.kt
================================================
package com.gustavoas.noti.notifications
import android.app.Notification
import android.content.Context
import android.service.notification.StatusBarNotification
import androidx.preference.PreferenceManager
import com.gustavoas.noti.ProgressBarAppsRepository
class DownloadProgressBar(
ctx: Context,
sbn: StatusBarNotification,
appsRepository: ProgressBarAppsRepository
): ProgressBarNotification(ctx, sbn, appsRepository) {
override val priorityLevel: Int = 2
init {
updateNotification(sbn)
}
override fun updateNotification(sbn: StatusBarNotification) {
super.updateNotification(sbn)
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(ctx)
val enabledForDownloads = sharedPrefs.getBoolean("showForDownloads", true)
if (!enabledForDownloads) {
cancel()
return
}
val (progress, progressMax) = getProgressBarValues(sbn)
if (progress == 0 && progressMax == 0) {
cancel()
return
}
sendProgressToAccessibilityService(progress, progressMax)
}
private fun getProgressBarValues(sbn: StatusBarNotification): Pair<Int, Int> {
val progress = sbn.notification.extras.getInt(Notification.EXTRA_PROGRESS)
val progressMax = sbn.notification.extras.getInt(Notification.EXTRA_PROGRESS_MAX)
return Pair(progress, progressMax)
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/notifications/GoogleTimerProgressBar.kt
================================================
package com.gustavoas.noti.notifications
import android.content.Context
import android.service.notification.StatusBarNotification
import com.gustavoas.noti.ProgressBarAppsRepository
class GoogleTimerProgressBar(
ctx: Context,
sbn: StatusBarNotification,
appsRepository: ProgressBarAppsRepository
): TimedProgressBar(ctx, sbn, appsRepository) {
override val priorityLevel: Int = 1
init {
updateNotification(sbn)
}
override fun updateNotification(sbn: StatusBarNotification) {
super.updateNotification(sbn)
val sortKey = sbn.notification.sortKey
if (sortKey == null || !sortKey.contains("RUNNING")) {
cancel()
return
}
val splitKey = sortKey.split("|")
val timeLeft = splitKey.firstOrNull { it.contains("⏳") } ?: return
val totalTime = splitKey.firstOrNull { it.contains("Σ") } ?: return
val timeLeftMillis = parseTimeStringToMillis(timeLeft.substringAfter("⏳"))
val totalTimeMillis = parseTimeStringToMillis(totalTime.substringAfter("Σ"))
startUpdatingTimedPosition(timeLeftMillis, totalTimeMillis, -1f)
}
private fun parseTimeStringToMillis(time: String): Long {
val timeSplit = time.trim().split(":")
val hours = timeSplit.getOrNull(0)?.toLongOrNull() ?: 0
val minutes = timeSplit.getOrNull(1)?.toLongOrNull() ?: 0
val seconds = timeSplit.getOrNull(2)?.toLongOrNull() ?: 0
return (hours * 3600 + minutes * 60 + seconds) * 1000
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/notifications/MediaProgressBar.kt
================================================
package com.gustavoas.noti.notifications
import android.content.Context
import android.media.MediaMetadata
import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.PlaybackState
import android.service.notification.StatusBarNotification
import androidx.core.app.NotificationCompat
import androidx.preference.PreferenceManager
import com.gustavoas.noti.ProgressBarAppsRepository
class MediaProgressBar(
ctx: Context,
sbn: StatusBarNotification,
appsRepository: ProgressBarAppsRepository
): TimedProgressBar(ctx, sbn, appsRepository) {
override val priorityLevel: Int = 0
private var mediaController: MediaController? = null
private var mediaCallback: MediaController.Callback? = null
init {
updateNotification(sbn)
}
override fun updateNotification(sbn: StatusBarNotification) {
super.updateNotification(sbn)
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(ctx)
val enabledForMedia = sharedPrefs.getBoolean("showForMedia", true)
if (!enabledForMedia) {
cancel()
return
}
val mediaSession =
sbn.notification.extras.get(NotificationCompat.EXTRA_MEDIA_SESSION) as? MediaSession.Token
if (mediaSession == null) {
cancel()
return
}
val newMediaController = MediaController(ctx, mediaSession)
if (mediaController == null) {
createProgressBarFromMedia(newMediaController)
}
}
private fun createProgressBarFromMedia(newMediaController: MediaController) {
mediaController = newMediaController
if (mediaController?.playbackState?.state == PlaybackState.STATE_PLAYING) {
startTrackingMediaPosition()
}
mediaCallback = object : MediaController.Callback() {
override fun onPlaybackStateChanged(state: PlaybackState?) {
super.onPlaybackStateChanged(state)
when (state?.state) {
PlaybackState.STATE_PLAYING -> {
startTrackingMediaPosition()
}
PlaybackState.STATE_NONE, PlaybackState.STATE_STOPPED,
PlaybackState.STATE_PAUSED, PlaybackState.STATE_ERROR -> {
cancel()
}
else -> {
stopUpdatingTimedPosition()
}
}
}
}
mediaController?.registerCallback(mediaCallback as MediaController.Callback)
}
private fun startTrackingMediaPosition() {
val artwork = mediaController?.metadata?.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART)
artwork?.let {
getColorFromBitmap(it)?.let { color ->
notificationColor = color
}
}
startUpdatingTimedPosition(
mediaController?.playbackState?.position ?: 0,
mediaController?.metadata?.getLong(MediaMetadata.METADATA_KEY_DURATION) ?: 0,
mediaController?.playbackState?.playbackSpeed ?: 1f
)
}
override fun cancel() {
super.cancel()
if (mediaCallback != null) {
mediaController?.unregisterCallback(mediaCallback as MediaController.Callback)
mediaCallback = null
}
mediaController = null
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/notifications/PercentageProgressBar.kt
================================================
package com.gustavoas.noti.notifications
import android.app.Notification
import android.content.Context
import android.service.notification.StatusBarNotification
import androidx.preference.PreferenceManager
import com.gustavoas.noti.ProgressBarAppsRepository
import kotlin.math.roundToInt
class PercentageProgressBar(
ctx: Context,
sbn: StatusBarNotification,
appsRepository: ProgressBarAppsRepository
): ProgressBarNotification(ctx, sbn, appsRepository) {
override val priorityLevel: Int = 2
private var initialValue: Int? = null
init {
updateNotification(sbn)
}
override fun updateNotification(sbn: StatusBarNotification) {
super.updateNotification(sbn)
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(ctx)
val enabledForDownloads = sharedPrefs.getBoolean("showForDownloads", true)
if (!enabledForDownloads) {
cancel()
return
}
val percentageProgress = getProgressFromPercentage(sbn)
if (percentageProgress == 0) {
cancel()
return
}
if (initialValue != null && initialValue != percentageProgress) {
sendProgressToAccessibilityService(percentageProgress, 100)
}
if (initialValue == null) {
initialValue = percentageProgress
}
}
private fun getProgressFromPercentage(sbn: StatusBarNotification): Int {
val extras = sbn.notification.extras
val title = extras.getCharSequence(Notification.EXTRA_TITLE)?.toString() ?: ""
val text = extras.getCharSequence(Notification.EXTRA_TEXT)?.toString() ?: ""
val subText = extras.getCharSequence(Notification.EXTRA_SUB_TEXT)?.toString() ?: ""
val bigText = extras.getCharSequence(Notification.EXTRA_BIG_TEXT)?.toString() ?: ""
val textLines = extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES)
val percentageProgress = title.substringBefore("%").toFloatOrNull() ?:
text.substringBefore("%").toFloatOrNull() ?:
subText.substringBefore("%").toFloatOrNull() ?:
bigText.split("\n").firstOrNull { it.contains("%") }?.toString()
?.substringBefore("%")?.toFloatOrNull() ?:
textLines?.firstOrNull { it.contains("%") }?.toString()
?.substringBefore("%")?.toFloatOrNull()
if (percentageProgress == null || percentageProgress.isNaN()) {
return 0
}
return percentageProgress.roundToInt()
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/notifications/ProgressBarNotification.kt
================================================
package com.gustavoas.noti.notifications
import android.app.Notification
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.os.Handler
import android.os.Looper
import android.service.notification.StatusBarNotification
import androidx.core.graphics.createBitmap
import androidx.palette.graphics.Palette
import androidx.preference.PreferenceManager
import com.gustavoas.noti.ProgressBarAppsRepository
import com.gustavoas.noti.R
import com.gustavoas.noti.Utils.getApplicationIcon
import com.gustavoas.noti.model.ProgressBarApp
import com.gustavoas.noti.model.ProgressNotification
import com.gustavoas.noti.services.AccessibilityService
import kotlin.math.roundToInt
abstract class ProgressBarNotification(
protected val ctx: Context,
private var sbn: StatusBarNotification,
private val appsRepository: ProgressBarAppsRepository
) {
protected abstract val priorityLevel: Int
protected var notificationColor = sbn.notification.color
private val removalHandler = Handler(Looper.getMainLooper())
open fun updateNotification(sbn: StatusBarNotification) {
this.sbn = sbn
}
open fun cancel() {
removalHandler.removeCallbacksAndMessages(null)
sendRemovalRequestToAccessibilityService()
}
protected fun sendProgressToAccessibilityService(
progress: Int = 0,
progressMax: Int = 0
) {
if (progressMax <= 0 || progress !in 0..progressMax) {
return
}
val appInDatabase = getOrCreateAppInDatabase()
updateProgressBarColor(appInDatabase)
if (!appInDatabase.showProgressBar) {
cancel()
return
}
val progressBarMax = ctx.resources.getInteger(R.integer.progress_bar_max)
val progressNormalized = if (progress == 0) {
0
} else {
(progress.toFloat() / progressMax.toFloat() * progressBarMax).roundToInt()
}
val intent = Intent(ctx, AccessibilityService::class.java)
intent.putExtra("id", sbn.key ?: "")
intent.putExtra(
"progressNotification",
ProgressNotification(
appInDatabase,
progressNormalized,
priorityLevel
)
)
ctx.startService(intent)
removalHandler.removeCallbacksAndMessages(null)
removalHandler.postDelayed({
sendRemovalRequestToAccessibilityService()
}, 10000)
}
private fun sendRemovalRequestToAccessibilityService() {
val intent = Intent(ctx, AccessibilityService::class.java)
intent.putExtra("id", sbn.key ?: "")
intent.putExtra("removal", true)
ctx.startService(intent)
}
private fun updateProgressBarColor(progressBarApp: ProgressBarApp) {
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(ctx)
val useNotificationColor = sharedPrefs.getBoolean("useNotificationColor", false)
if (!progressBarApp.useDefaultColor || !useNotificationColor) {
return
}
if (notificationColor == Notification.COLOR_DEFAULT) {
val appIcon = getApplicationIcon(ctx, sbn.packageName)
appIcon?.let {
getColorFromBitmap(drawableToBitmap(it))?.let { color ->
notificationColor = color
}
}
}
if (notificationColor != Notification.COLOR_DEFAULT && progressBarApp.color != notificationColor) {
progressBarApp.color = notificationColor
appsRepository.updateApp(progressBarApp)
}
}
protected fun getColorFromBitmap(bitmap: Bitmap): Int? {
val palette = Palette.from(bitmap).generate()
val swatch = palette.lightMutedSwatch
?: palette.vibrantSwatch
?: palette.dominantSwatch
return swatch?.rgb
}
private fun drawableToBitmap(drawable: Drawable): Bitmap {
if (drawable is BitmapDrawable) return drawable.bitmap
val width = drawable.intrinsicWidth.takeIf { it > 0 } ?: 1
val height = drawable.intrinsicHeight.takeIf { it > 0 } ?: 1
val bitmap = createBitmap(width, height)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
return bitmap
}
private fun getOrCreateAppInDatabase(): ProgressBarApp {
return appsRepository.let {
it.getApp(sbn.packageName ?: "") ?: it.addApp(ProgressBarApp(sbn.packageName, true))
}
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/notifications/TimedProgressBar.kt
================================================
package com.gustavoas.noti.notifications
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.service.notification.StatusBarNotification
import com.gustavoas.noti.ProgressBarAppsRepository
import kotlin.math.abs
abstract class TimedProgressBar(
ctx: Context,
sbn: StatusBarNotification,
appsRepository: ProgressBarAppsRepository
): ProgressBarNotification(ctx, sbn, appsRepository) {
private val updateInterval = 1000
private val handler = Handler(Looper.getMainLooper())
private var updatesRunnable: Runnable? = null
protected fun startUpdatingTimedPosition(
initialPosition: Long,
duration: Long,
speed: Float
) {
stopUpdatingTimedPosition()
val initialTime = System.currentTimeMillis()
updatesRunnable = object : Runnable {
override fun run() {
val currTime = System.currentTimeMillis()
var currProgress = (initialPosition + (currTime - initialTime) * speed).toLong()
if (currProgress !in 0..duration) {
return
}
val updateStep = abs(updateInterval * speed).toInt()
if (duration - currProgress < updateStep) {
currProgress = duration
} else if (currProgress < updateStep) {
currProgress = 0
}
sendProgressToAccessibilityService(
currProgress.toInt(),
duration.toInt(),
)
updatesRunnable?.let { handler.postDelayed(it, updateInterval.toLong()) }
}
}
updatesRunnable?.let { handler.post(it) }
}
protected fun stopUpdatingTimedPosition() {
handler.removeCallbacksAndMessages(null)
updatesRunnable = null
}
override fun cancel() {
super.cancel()
stopUpdatingTimedPosition()
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/preferences/BannerPreference.kt
================================================
package com.gustavoas.noti.preferences
import android.content.Context
import android.util.AttributeSet
import android.widget.Button
import android.widget.ImageButton
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.gustavoas.noti.R
class BannerPreference(context: Context, attrs: AttributeSet): Preference(context, attrs) {
var onBtnClick = { intent?.let { context.startActivity(intent) } }
init {
layoutResource = R.layout.banner_preference
isSelectable = false
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
val persistedValue = getPersistedBoolean(true)
val preferenceHolder = holder.itemView
if (!persistedValue) {
this.isVisible = false
return
}
val btn = preferenceHolder.findViewById<Button>(R.id.bannerBtn)
btn.text = summary
btn.setOnClickListener {
onBtnClick()
}
val closeBtn = preferenceHolder.findViewById<ImageButton>(R.id.closeBtn)
closeBtn.setOnClickListener {
this.isVisible = false
persistBoolean(false)
}
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/preferences/BarStylesListPreference.kt
================================================
package com.gustavoas.noti.preferences
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.ArrayAdapter
import android.widget.ListView
import androidx.appcompat.app.AlertDialog
import androidx.preference.ListPreference
import com.gustavoas.noti.R
class BarStylesListPreference(context: Context, attrs: AttributeSet?) : ListPreference(context, attrs) {
override fun onClick() {
val sharedPreferences = preferenceManager.sharedPreferences ?: return
if (!sharedPreferences.getBoolean("advancedProgressBarStyle", false)) {
AlertDialog.Builder(context)
.setSingleChoiceItems(entries, getValueIndex()) { dialog, index ->
if (callChangeListener(entryValues[index].toString())) {
setValueIndex(index)
}
dialog.dismiss()
}.setNeutralButton(context.resources.getString(R.string.advancedOptions)) { _, _ ->
showAdvancedDialog()
}.setTitle(title).show()
} else {
showAdvancedDialog()
}
}
private fun showAdvancedDialog() {
val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val dialogView = layoutInflater.inflate(R.layout.advanced_style_dialog, null)
val portraitStyleListview = dialogView.findViewById<ListView>(R.id.portraitStyleOptions)
val landscapeStyleListview = dialogView.findViewById<ListView>(R.id.landscapeStyleOptions)
val options = context.resources.getStringArray(R.array.progressBarStyle)
val adapter = ArrayAdapter(context, androidx.appcompat.R.layout.select_dialog_singlechoice_material, options)
portraitStyleListview.adapter = adapter
landscapeStyleListview.adapter = adapter
var viewHeight = 0
for (i in 0 until adapter.count) {
val view = adapter.getView(i, null, portraitStyleListview)
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
viewHeight += view.measuredHeight
}
portraitStyleListview.layoutParams.height = viewHeight
landscapeStyleListview.layoutParams.height = viewHeight
val sharedPreferences = preferenceManager.sharedPreferences ?: return
if (!sharedPreferences.contains("progressBarStylePortrait") && !sharedPreferences.contains("progressBarStyleLandscape")) {
sharedPreferences.edit()
.putString(
"progressBarStylePortrait",
sharedPreferences.getString("progressBarStyle", "linear")
)
.putString(
"progressBarStyleLandscape",
sharedPreferences.getString("progressBarStyle", "linear")
)
.apply()
}
portraitStyleListview.setItemChecked(
context.resources.getStringArray(R.array.progressBarStyleValues).indexOf(
sharedPreferences.getString("progressBarStylePortrait", "linear")
), true
)
landscapeStyleListview.setItemChecked(
context.resources.getStringArray(R.array.progressBarStyleValues).indexOf(
sharedPreferences.getString("progressBarStyleLandscape", "linear")
), true
)
AlertDialog.Builder(context)
.setView(dialogView)
.setPositiveButton(android.R.string.ok) { _, _ ->
sharedPreferences.edit()?.putBoolean("advancedProgressBarStyle", true)?.apply()
sharedPreferences.edit()
.putString(
"progressBarStylePortrait",
context.resources.getStringArray(R.array.progressBarStyleValues)[portraitStyleListview.checkedItemPosition]
)
.putString(
"progressBarStyleLandscape",
context.resources.getStringArray(R.array.progressBarStyleValues)[landscapeStyleListview.checkedItemPosition]
)
.apply()
}
.setNegativeButton(android.R.string.cancel, null)
.setNeutralButton(context.resources.getString(R.string.reset)) { _, _ ->
sharedPreferences.edit()?.putBoolean("advancedProgressBarStyle", false)?.apply()
}
.show()
}
private fun getValueIndex() = entryValues.indexOf(value)
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/preferences/SeekBarPreference.kt
================================================
package com.gustavoas.noti.preferences
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import com.google.android.material.slider.Slider
import com.gustavoas.noti.R
import com.gustavoas.noti.Utils.vibrate
class SeekBarPreference(context: Context, attrs: AttributeSet): Preference(context, attrs) {
private var preferenceHolder: View? = null
private var minNumber: Int = -50
private var maxNumber: Int = 50
private var defaultValue: Int = 0
private var currentValue: Int = defaultValue
init {
layoutResource = R.layout.seekbar_preference
val attributes = context.obtainStyledAttributes(attrs, R.styleable.HorizontalNumberPicker)
minNumber = attributes.getInteger(R.styleable.HorizontalNumberPicker_min_number, minNumber)
maxNumber = attributes.getInteger(R.styleable.HorizontalNumberPicker_max_number, maxNumber)
defaultValue = attributes.getInteger(R.styleable.HorizontalNumberPicker_default_value, defaultValue)
attributes.recycle()
}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
preferenceHolder = holder.itemView
val seekBar = preferenceHolder?.findViewById<Slider>(R.id.slider)
currentValue = getPersistedInt(defaultValue)
seekBar?.value = currentValue.toFloat()
seekBar?.valueFrom = minNumber.toFloat()
seekBar?.valueTo = maxNumber.toFloat()
seekBar?.addOnChangeListener { _, value, _ ->
if (value.toInt() != currentValue) {
currentValue = value.toInt()
persistInt(currentValue)
vibrate(context)
}
}
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/services/AccessibilityService.kt
================================================
package com.gustavoas.noti.services
import android.accessibilityservice.AccessibilityService
import android.animation.ObjectAnimator
import android.app.KeyguardManager
import android.content.BroadcastReceiver
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.res.Configuration
import android.graphics.PixelFormat
import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Handler
import android.os.Looper
import android.view.Display
import android.view.Gravity
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
import android.view.accessibility.AccessibilityEvent
import android.view.animation.DecelerateInterpolator
import android.widget.FrameLayout
import androidx.core.content.ContextCompat
import androidx.core.hardware.display.DisplayManagerCompat
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.progressindicator.LinearProgressIndicator
import com.gustavoas.noti.R
import com.gustavoas.noti.Utils.getColorForApp
import com.gustavoas.noti.Utils.getScreenLargeSide
import com.gustavoas.noti.Utils.getScreenSmallSide
import com.gustavoas.noti.Utils.getStatusBarHeight
import com.gustavoas.noti.Utils.hasAccessibilityPermission
import com.gustavoas.noti.Utils.hasSystemAlertWindowPermission
import com.gustavoas.noti.model.ProgressBarApp
import com.gustavoas.noti.model.ProgressNotification
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.roundToInt
import kotlin.math.sqrt
class AccessibilityService : AccessibilityService() {
private val windowManager by lazy { getSystemService(WINDOW_SERVICE) as WindowManager }
private val displayManager by lazy { DisplayManagerCompat.getInstance(this) }
private val keyguardManager by lazy { getSystemService(KEYGUARD_SERVICE) as KeyguardManager }
private lateinit var overlayView: View
private lateinit var progressBar: LinearProgressIndicator
private lateinit var circularProgressBar: CircularProgressIndicator
private val handler = Handler(Looper.getMainLooper())
private val activeProgressBars = mutableMapOf<String, ProgressNotification>()
private val fullscreenDetectionService by lazy {
Intent(this, FullscreenDetectionService::class.java)
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
override fun onInterrupt() {}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (!hasAccessibilityPermission(this) && !hasSystemAlertWindowPermission(this)) {
return super.onStartCommand(intent, flags, startId)
}
val id = intent?.getStringExtra("id") ?: ""
val removal = intent?.getBooleanExtra("removal", false) ?: false
if (removal) {
activeProgressBars.remove(id)
} else {
val newProgressNotification = if (SDK_INT > Build.VERSION_CODES.TIRAMISU) {
intent?.getParcelableExtra("progressNotification", ProgressNotification::class.java)
} else {
intent?.getParcelableExtra("progressNotification")
}
newProgressNotification?.let {
activeProgressBars[id] = it
}
}
updateProgressOverlay(1000)
return super.onStartCommand(intent, flags, startId)
}
private fun updateProgressOverlay(hideDelay: Long = 0) {
val showInLockScreen = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("showInLockScreen", true)
if (activeProgressBars.isEmpty() || (isLocked() && !showInLockScreen)) {
if (!this::overlayView.isInitialized || !overlayView.isShown) {
return
}
handler.postDelayed({
hideProgressBarIn(hideDelay / 2)
}, hideDelay)
} else {
getActiveNotification()?.let {
showOverlayWithProgress(it)
}
}
}
private fun getActiveNotification(): ProgressNotification? {
val highestPriority = activeProgressBars.values.maxOfOrNull { it.priority } ?: 0
return activeProgressBars.values
.filter { it.priority >= highestPriority }
.maxByOrNull { it.progress }
}
private fun showOverlayWithProgress(notification: ProgressNotification) {
handler.removeCallbacksAndMessages(null)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val progressBarStyle =
if (sharedPreferences.getBoolean(getSizeDependentPreferenceKey("advancedProgressBarStyle").first, false)) {
sharedPreferences.getString(
if (isInPortraitMode()) {
getSizeDependentPreferenceKey("progressBarStylePortrait").first
} else {
getSizeDependentPreferenceKey("progressBarStyleLandscape").first
}, "linear"
)
} else {
sharedPreferences.getString(getSizeDependentPreferenceKey("progressBarStyle").first, "linear")
}
if(progressBarStyle == "none") {
if (this::overlayView.isInitialized && overlayView.isShown) {
hideProgressBarIn()
}
return
}
if (!this::overlayView.isInitialized || !overlayView.isShown) {
inflateOverlay()
}
progressBar = overlayView.findViewById(R.id.progressBar)
circularProgressBar = overlayView.findViewById(R.id.circularProgressBar)
if (progressBarStyle == "circular") {
progressBar.visibility = View.GONE
circularProgressBar.visibility = View.VISIBLE
circularProgressBarCustomizations(sharedPreferences)
} else {
progressBar.visibility = View.VISIBLE
circularProgressBar.visibility = View.GONE
linearProgressBarCustomizations(sharedPreferences)
}
applyCommonProgressBarCustomizations(sharedPreferences, notification.progressBarApp)
animateProgressBarTo(notification.progress, progressBarStyle == "circular")
// TODO: record timestamp and deprioritize after a while
// handler.postDelayed({
// updateProgressOverlay()
// }, 3000)
}
private fun circularProgressBarCustomizations(sharedPrefs: SharedPreferences) {
val trackThickness = max(sharedPrefs.getSizeDependentInt("circularProgressBarThickness", 15), 0)
val cutoutSize = max(sharedPrefs.getSizeDependentInt("circularProgressBarSize", 65), 0)
circularProgressBar.indicatorSize = cutoutSize + 2 * trackThickness
circularProgressBar.trackThickness = trackThickness
val marginTop = sharedPrefs.getSizeDependentInt("circularProgressBarTopOffset", 60) - trackThickness - cutoutSize / 2
val horizontalOffset = sharedPrefs.getSizeDependentInt("circularProgressBarHorizontalOffset", 0)
val overlayParams = overlayView.layoutParams as WindowManager.LayoutParams
overlayParams.width = circularProgressBar.indicatorSize
overlayParams.height = circularProgressBar.indicatorSize
val displayRotation = displayManager.getDisplay(Display.DEFAULT_DISPLAY)!!.rotation
circularProgressBar.rotation = (360 - displayRotation.toFloat() * 90) % 360
val progressParams = circularProgressBar.layoutParams as FrameLayout.LayoutParams
var paddingTop = 0
var paddingRight = 0
var paddingLeft = 0
if (marginTop < 0) {
paddingTop = marginTop
} else if (marginTop + circularProgressBar.indicatorSize > getScreenLargeSide(this)) {
paddingTop = marginTop + circularProgressBar.indicatorSize - getScreenLargeSide(this)
}
val halfWidth = getScreenSmallSide(this) / 2
val halfIndicatorSize = circularProgressBar.indicatorSize / 2
if (abs(horizontalOffset) + halfIndicatorSize > halfWidth) {
if (horizontalOffset - halfIndicatorSize < -halfWidth) {
paddingLeft = halfWidth + horizontalOffset - halfIndicatorSize
} else {
paddingLeft = -halfWidth + horizontalOffset + halfIndicatorSize
}
if (horizontalOffset + halfIndicatorSize > halfWidth) {
paddingRight = halfWidth - horizontalOffset - halfIndicatorSize
} else {
paddingRight = -halfWidth - horizontalOffset + halfIndicatorSize
}
}
when(displayRotation) {
Surface.ROTATION_0 -> {
overlayParams.gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
overlayParams.x = horizontalOffset
overlayParams.y = marginTop
progressParams.setMargins(paddingLeft, paddingTop, -paddingLeft, -paddingTop)
}
Surface.ROTATION_180 -> {
overlayParams.gravity = Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM
overlayParams.x = -horizontalOffset
overlayParams.y = marginTop
progressParams.setMargins(paddingRight, -paddingTop, -paddingRight, paddingTop)
}
Surface.ROTATION_90 -> {
// landscape, device top is on the left
overlayParams.gravity = Gravity.CENTER_VERTICAL or Gravity.LEFT
overlayParams.x = marginTop
overlayParams.y = -horizontalOffset
progressParams.setMargins(paddingTop, paddingRight, -paddingTop, -paddingRight)
}
Surface.ROTATION_270 -> {
// landscape, device top is on the right
overlayParams.gravity = Gravity.CENTER_VERTICAL or Gravity.RIGHT
overlayParams.x = marginTop
overlayParams.y = horizontalOffset
progressParams.setMargins(-paddingTop, paddingLeft, paddingTop, -paddingLeft)
}
}
if (SDK_INT >= Build.VERSION_CODES.P) {
overlayParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
circularProgressBar.layoutParams = progressParams
try {
windowManager.updateViewLayout(overlayView, overlayParams)
} catch (_: Exception) {}
}
private fun linearProgressBarCustomizations(sharedPrefs: SharedPreferences) {
if (sharedPrefs.getBoolean(getSizeDependentPreferenceKey("matchStatusBarHeight").first, false)) {
progressBar.trackThickness = getStatusBarHeight(this)
} else {
progressBar.trackThickness = sharedPrefs.getSizeDependentInt("linearProgressBarSize", 15)
}
val paddingTop = sharedPrefs.getSizeDependentInt("linearProgressBarMarginTop", 0)
val overlayParams = overlayView.layoutParams as WindowManager.LayoutParams
if (SDK_INT >= Build.VERSION_CODES.P) {
if (sharedPrefs.getBoolean(getSizeDependentPreferenceKey("showBelowNotch").first, false)) {
overlayParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER
} else {
overlayParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
}
overlayParams.gravity = Gravity.TOP
overlayParams.width = ViewGroup.LayoutParams.MATCH_PARENT
overlayParams.height = progressBar.trackThickness + paddingTop
overlayParams.y = paddingTop
try {
windowManager.updateViewLayout(overlayView, overlayParams)
} catch (_: Exception) {}
}
private fun applyCommonProgressBarCustomizations(sharedPrefs: SharedPreferences, progressBarApp: ProgressBarApp) {
val progressBarColor = getColorForApp(this, progressBarApp)
progressBar.setIndicatorColor(progressBarColor)
circularProgressBar.setIndicatorColor(progressBarColor)
val blackBackground = sharedPrefs.getBoolean("blackBackground", false)
val backgroundColor = if (blackBackground) {
ContextCompat.getColor(this, android.R.color.black)
} else {
ContextCompat.getColor(this, android.R.color.transparent)
}
progressBar.trackColor = backgroundColor
circularProgressBar.trackColor = backgroundColor
val useRoundedCorners = sharedPrefs.getBoolean("useRoundedCorners", false)
if (useRoundedCorners) {
progressBar.trackCornerRadius = 100
circularProgressBar.trackCornerRadius = 100
} else {
progressBar.trackCornerRadius = 0
circularProgressBar.trackCornerRadius = 0
}
}
private fun SharedPreferences.getSizeDependentInt(key: String, defaultValue: Int): Int {
val sizeDependentPreference = getSizeDependentPreferenceKey(key)
return this.getInt(sizeDependentPreference.first, defaultValue).times(sizeDependentPreference.second).roundToInt()
}
private fun getSizeDependentPreferenceKey(key: String): Pair<String, Float> {
val width = getScreenSmallSide(this)
val height = getScreenLargeSide(this)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
val relatedPrefs = sharedPreferences.all.filterKeys { it.startsWith(key) }
val sameRatio = relatedPrefs.filterKeys { it.length > key.length && it[key.length].isDigit() && it.drop(key.length).split("x")[0].toInt() / it.drop(key.length).split("x")[1].toInt() == height / width }
if (sameRatio.isEmpty()) {
return Pair(key, 1f)
}
val closest = sameRatio.keys.minByOrNull { abs(it.drop(key.length).split("x")[0].toInt() * it.drop(key.length).split("x")[1].toInt() - height * width) }
val reductionRatio = sqrt((height * width).div(closest!!.drop(key.length).split("x")[0].toFloat() * closest.drop(key.length).split("x")[1].toFloat()))
return Pair(closest, reductionRatio)
}
private fun isInPortraitMode(): Boolean {
return resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
}
private fun isLocked(): Boolean {
return keyguardManager.isKeyguardLocked
}
private fun setProgressToZero() {
progressBar.progress = 0
circularProgressBar.progress = 0
}
private fun hideProgressBarIn(delay: Long = 500) {
progressBar.hide()
circularProgressBar.hide()
handler.postDelayed({
setProgressToZero()
hideOverlay()
}, delay)
}
private fun animateProgressBarTo(progress: Int, animateCircularProgressBar: Boolean = false) {
if (animateCircularProgressBar) {
circularProgressBar.show()
val circularProgressAnimation =
ObjectAnimator.ofInt(circularProgressBar, "progress", progress)
circularProgressAnimation.duration = 250
circularProgressAnimation.interpolator = DecelerateInterpolator()
circularProgressAnimation.start()
progressBar.progress = progress
} else {
progressBar.show()
val progressAnimation = ObjectAnimator.ofInt(progressBar, "progress", progress)
progressAnimation.duration = 250
progressAnimation.interpolator = DecelerateInterpolator()
progressAnimation.start()
circularProgressBar.progress = progress
}
}
private fun inflateOverlay() {
if (!this::overlayView.isInitialized) {
overlayView = View.inflate(this, R.layout.progress_bar, null)
}
val hasSAWPermission = hasSystemAlertWindowPermission(this)
val hasAccessibilityPermission = hasAccessibilityPermission(this)
val showInLockscreen = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean("showInLockScreen", true) && hasAccessibilityPermission
val windowType = if (SDK_INT >= Build.VERSION_CODES.O && hasSAWPermission && !showInLockscreen) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else if (SDK_INT >= Build.VERSION_CODES.M && hasAccessibilityPermission) {
WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY
} else if (SDK_INT < Build.VERSION_CODES.O && hasSAWPermission) {
@Suppress("DEPRECATION")
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
} else {
return
}
val params = WindowManager.LayoutParams (
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
windowType,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION or
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS,
PixelFormat.TRANSLUCENT
)
if (SDK_INT >= Build.VERSION_CODES.S && hasSAWPermission && !showInLockscreen) {
params.alpha = 0.8f
}
if (!overlayView.isShown) {
try {
windowManager.addView(overlayView, params)
} catch (_: Exception) {
// TODO
return
}
if (hasSAWPermission) {
overlayView.alpha = 0f
LocalBroadcastManager.getInstance(this).registerReceiver(
fullscreenDetectionReceiver,
IntentFilter(FullscreenDetectionService::class.java.simpleName)
)
startService(fullscreenDetectionService)
}
}
}
private val fullscreenDetectionReceiver = object : BroadcastReceiver() {
override fun onReceive(context: android.content.Context?, intent: Intent?) {
val disableInFullscreen = PreferenceManager.getDefaultSharedPreferences(this@AccessibilityService)
.getBoolean("disableInFullScreen", true)
val isFullscreen = intent?.getBooleanExtra("isFullscreen", false) ?: false
val alpha = if (disableInFullscreen && isFullscreen) 0f else 1f
overlayView.animate()
.alpha(alpha)
.setDuration(300)
.start()
}
}
private fun hideOverlay() {
if (this::overlayView.isInitialized && overlayView.isShown) {
windowManager.removeView(overlayView)
}
stopService(fullscreenDetectionService)
LocalBroadcastManager.getInstance(this).unregisterReceiver(fullscreenDetectionReceiver)
stopSelf()
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
updateProgressOverlay(0)
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/services/FullscreenDetectionService.kt
================================================
package com.gustavoas.noti.services
import android.app.KeyguardManager
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.graphics.PixelFormat
import android.os.Build
import android.view.View
import android.view.ViewTreeObserver
import android.view.WindowManager
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.gustavoas.noti.R
import com.gustavoas.noti.Utils.getRealDisplayHeight
import com.gustavoas.noti.Utils.hasSystemAlertWindowPermission
class FullscreenDetectionService : Service() {
private val windowManager by lazy { getSystemService(WINDOW_SERVICE) as WindowManager }
private lateinit var fullscreenDetectionView: View
private lateinit var globalLayoutListener: ViewTreeObserver.OnGlobalLayoutListener
private var displayOffReceiver: BroadcastReceiver? = null
override fun onBind(intent: Intent?) = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (!hasSystemAlertWindowPermission(this)) {
return super.onStartCommand(intent, flags, startId)
}
if (!this::fullscreenDetectionView.isInitialized || !fullscreenDetectionView.isShown) {
inflateFullscreenDetectionOverlay()
}
registerDisplayOffReceiver()
return super.onStartCommand(intent, flags, startId)
}
private fun inflateFullscreenDetectionOverlay() {
if (!this::fullscreenDetectionView.isInitialized) {
fullscreenDetectionView = View.inflate(this, R.layout.progress_bar, null)
}
val windowType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
@Suppress("DEPRECATION")
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
}
val fullscreenDetectorParams = WindowManager.LayoutParams(
0,
WindowManager.LayoutParams.MATCH_PARENT,
windowType,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT
)
fullscreenDetectorParams.alpha = 0f
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
fullscreenDetectorParams.layoutInDisplayCutoutMode =
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
}
windowManager.addView(fullscreenDetectionView, fullscreenDetectorParams)
globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
val isKeyguardShown = keyguardManager.isKeyguardLocked
broadcastFullscreenState(
if (!isKeyguardShown) {
isFullScreen()
} else {
false
}
)
}
fullscreenDetectionView.viewTreeObserver.addOnGlobalLayoutListener(
globalLayoutListener
)
}
private fun isFullScreen(): Boolean {
val displayHeight = getRealDisplayHeight(this)
return fullscreenDetectionView.height == displayHeight
}
private fun broadcastFullscreenState(isFullscreen: Boolean) {
val intent = Intent(this.javaClass.simpleName)
intent.putExtra("isFullscreen", isFullscreen)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
}
private fun registerDisplayOffReceiver() {
if (displayOffReceiver != null) {
return
}
displayOffReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_SCREEN_OFF) {
broadcastFullscreenState(false)
}
}
}
registerReceiver(displayOffReceiver, IntentFilter(Intent.ACTION_SCREEN_OFF))
}
private fun unregisterDisplayOffReceiver() {
displayOffReceiver?.let {
try {
unregisterReceiver(it)
} catch (_: IllegalArgumentException) {}
displayOffReceiver = null
}
}
override fun onDestroy() {
super.onDestroy()
if (this::fullscreenDetectionView.isInitialized && fullscreenDetectionView.isShown) {
windowManager.removeView(fullscreenDetectionView)
if (fullscreenDetectionView.viewTreeObserver.isAlive) {
fullscreenDetectionView.viewTreeObserver.removeOnGlobalLayoutListener(globalLayoutListener)
}
}
unregisterDisplayOffReceiver()
}
}
================================================
FILE: app/src/main/java/com/gustavoas/noti/services/NotificationListenerService.kt
================================================
package com.gustavoas.noti.services
import android.app.Notification
import android.os.Process
import android.service.notification.NotificationListenerService
import android.service.notification.StatusBarNotification
import androidx.core.app.NotificationCompat
import androidx.preference.PreferenceManager
import com.gustavoas.noti.ProgressBarAppsRepository
import com.gustavoas.noti.notifications.DownloadProgressBar
import com.gustavoas.noti.notifications.GoogleTimerProgressBar
import com.gustavoas.noti.notifications.MediaProgressBar
import com.gustavoas.noti.notifications.PercentageProgressBar
import com.gustavoas.noti.notifications.ProgressBarNotification
import kotlin.math.roundToInt
class NotificationListenerService : NotificationListenerService() {
private val appsRepository by lazy { ProgressBarAppsRepository(this) }
private val activeProgressBars = mutableMapOf<String, ProgressBarNotification>()
private fun isMediaNotification(sbn: StatusBarNotification): Boolean {
val extras = sbn.notification.extras
val template = extras.getString(Notification.EXTRA_TEMPLATE)
val templateMatch = Notification.MediaStyle::class.java.name
if (template != templateMatch) {
return false
}
return sbn.notification.extras.containsKey(NotificationCompat.EXTRA_MEDIA_SESSION)
}
private fun isDownloadNotification(sbn: StatusBarNotification): Boolean {
val extras = sbn.notification.extras
val hasProgress = extras.getInt(NotificationCompat.EXTRA_PROGRESS) > 0
val hasProgressMax = extras.getInt(NotificationCompat.EXTRA_PROGRESS_MAX) > 0
val isIndeterminate = extras.getBoolean(NotificationCompat.EXTRA_PROGRESS_INDETERMINATE)
return hasProgress && hasProgressMax && !isIndeterminate
}
// TODO Do this differently
private fun getProgressFromPercentage(sbn: StatusBarNotification): Int {
val extras = sbn.notification.extras
val title = extras.getCharSequence(Notification.EXTRA_TITLE)?.toString() ?: ""
val text = extras.getCharSequence(Notification.EXTRA_TEXT)?.toString() ?: ""
val subText = extras.getCharSequence(Notification.EXTRA_SUB_TEXT)?.toString() ?: ""
val bigText = extras.getCharSequence(Notification.EXTRA_BIG_TEXT)?.toString() ?: ""
val textLines = extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES)
val percentageProgress = title.substringBefore("%").toFloatOrNull() ?:
text.substringBefore("%").toFloatOrNull() ?:
subText.substringBefore("%").toFloatOrNull() ?:
bigText.split("\n").firstOrNull { it.contains("%") }?.toString()
?.substringBefore("%")?.toFloatOrNull() ?:
textLines?.firstOrNull { it.contains("%") }?.toString()
?.substringBefore("%")?.toFloatOrNull()
if (percentageProgress == null || percentageProgress.isNaN()) {
return 0
}
return percentageProgress.roundToInt()
}
private fun isGoogleTimerNotification(sbn: StatusBarNotification): Boolean {
if (sbn.packageName != "com.google.android.deskclock") {
return false
}
val sortKey = sbn.notification.sortKey
return !sortKey.isNullOrEmpty()
}
private fun shouldShowProgressForNotification(sbn: StatusBarNotification): Boolean {
if (sbn.packageName == this.packageName) {
return false
}
val uniqueIdentifier = sbn.key ?: return false
if (activeProgressBars.containsKey(uniqueIdentifier)) {
return true
}
if (!showForApp(sbn.packageName ?: "")) {
return false
}
val sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this)
if (isMediaNotification(sbn)) {
val enabledForMedia = sharedPrefs.getBoolean("showForMedia", true)
return enabledForMedia
}
if (isDownloadNotification(sbn) || getProgressFromPercentage(sbn) > 0) {
val enabledForDownloads = sharedPrefs.getBoolean("showForDownloads", true)
return enabledForDownloads
}
return isGoogleTimerNotification(sbn)
}
override fun onNotificationPosted(sbn: StatusBarNotification) {
super.onNotificationPosted(sbn)
val notificationUser = sbn.user
val packageUser = Process.myUserHandle()
if (notificationUser != packageUser) {
return
}
if (shouldShowProgressForNotification(sbn)) {
getProgressFromNotification(sbn)
}
}
private fun getProgressFromNotification(sbn: StatusBarNotification) {
val uniqueIdentifier = sbn.key.toString()
if (activeProgressBars.containsKey(uniqueIdentifier)) {
val notification = activeProgressBars[uniqueIdentifier]
notification?.updateNotification(sbn)
return
}
activeProgressBars[uniqueIdentifier] = if (isMediaNotification(sbn)) {
MediaProgressBar(this, sbn, appsRepository)
} else if (isGoogleTimerNotification(sbn)) {
GoogleTimerProgressBar(this, sbn, appsRepository)
} else if (isDownloadNotification(sbn)) {
DownloadProgressBar(this, sbn, appsRepository)
} else if (getProgressFromPercentage(sbn) > 0) {
PercentageProgressBar(this, sbn, appsRepository)
} else {
return
}
}
override fun onNotificationRemoved(sbn: StatusBarNotification) {
super.onNotificationRemoved(sbn)
val activeProgressBar = activeProgressBars.remove(sbn.key.toString())
activeProgressBar?.cancel()
}
private fun showForApp(packageName: String): Boolean {
return packageName.isNotEmpty() && appsRepository.showProgressForApp(packageName) != false
}
override fun onDestroy() {
super.onDestroy()
activeProgressBars.values.forEach { it.cancel() }
activeProgressBars.clear()
appsRepository.close()
}
}
================================================
FILE: app/src/main/res/color/outlined_button_selector.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorPrimaryContainer" android:state_checked="true" />
<item android:color="@android:color/transparent" />
</selector>
================================================
FILE: app/src/main/res/drawable/circular_mask.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="@color/app_accent_color" />
</shape>
</item>
</ripple>
================================================
FILE: app/src/main/res/drawable/ic_accessibility.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M480,240Q447,240 424,217Q400,193 400,160Q400,127 424,104Q447,80 480,80Q513,80 537,104Q560,127 560,160Q560,193 537,217Q513,240 480,240ZM360,880L360,360Q300,355 238,345Q176,335 120,320L140,240Q218,261 306,271Q394,280 480,280Q566,280 654,271Q742,261 820,240L840,320Q784,335 722,345Q660,355 600,360L600,880L520,880L520,640L440,640L440,880L360,880Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_apps.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M240,800Q207,800 184,777Q160,753 160,720Q160,687 184,664Q207,640 240,640Q273,640 297,664Q320,687 320,720Q320,753 297,777Q273,800 240,800ZM480,800Q447,800 424,777Q400,753 400,720Q400,687 424,664Q447,640 480,640Q513,640 537,664Q560,687 560,720Q560,753 537,777Q513,800 480,800ZM720,800Q687,800 664,777Q640,753 640,720Q640,687 664,664Q687,640 720,640Q753,640 777,664Q800,687 800,720Q800,753 777,777Q753,800 720,800ZM240,560Q207,560 184,537Q160,513 160,480Q160,447 184,424Q207,400 240,400Q273,400 297,424Q320,447 320,480Q320,513 297,537Q273,560 240,560ZM480,560Q447,560 424,537Q400,513 400,480Q400,447 424,424Q447,400 480,400Q513,400 537,424Q560,447 560,480Q560,513 537,537Q513,560 480,560ZM720,560Q687,560 664,537Q640,513 640,480Q640,447 664,424Q687,400 720,400Q753,400 777,424Q800,447 800,480Q800,513 777,537Q753,560 720,560ZM240,320Q207,320 184,297Q160,273 160,240Q160,207 184,184Q207,160 240,160Q273,160 297,184Q320,207 320,240Q320,273 297,297Q273,320 240,320ZM480,320Q447,320 424,297Q400,273 400,240Q400,207 424,184Q447,160 480,160Q513,160 537,184Q560,207 560,240Q560,273 537,297Q513,320 480,320ZM720,320Q687,320 664,297Q640,273 640,240Q640,207 664,184Q687,160 720,160Q753,160 777,184Q800,207 800,240Q800,273 777,297Q753,320 720,320Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_bug_report.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="48"
android:viewportHeight="48"
android:tint="@color/text">
<path
android:fillColor="#000000"
android:pathData="M36.8,17.6h-4.5c-0.7,-1.2 -1.7,-2.3 -2.9,-3.1l2.6,-2.6l-2.2,-2.2l-3.5,3.5c-0.7,-0.2 -1.5,-0.3 -2.3,-0.3s-1.5,0.1 -2.2,0.3l-3.5,-3.5L16,11.9l2.6,2.6c-1.2,0.8 -2.2,1.9 -2.9,3.1h-4.5v3.2h3.3c-0.1,0.5 -0.1,1.1 -0.1,1.6V24h-3.2v3.2h3.2v1.6c0,0.5 0.1,1.1 0.1,1.6h-3.3v3.2h4.5c1.7,2.9 4.7,4.8 8.3,4.8s6.6,-1.9 8.3,-4.8h4.5v-3.2h-3.3c0.1,-0.5 0.1,-1.1 0.1,-1.6v-1.6h3.2V24h-3.2v-1.6c0,-0.5 -0.1,-1.1 -0.1,-1.6h3.3V17.6zM27.2,30.4h-6.4v-3.2h6.4V30.4zM27.2,24h-6.4v-3.2h6.4V24z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_calentile.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="28dp"
android:width="28dp"
android:viewportHeight="48"
android:viewportWidth="48"
android:tint="@color/text">
<path android:fillColor="#000000" android:pathData="M20.8,29a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path android:fillColor="#000000" android:pathData="M20.8,21.1a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path android:fillColor="#000000" android:pathData="M20.8,36.9a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path android:fillColor="#000000" android:pathData="M29.7,21.1a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path android:fillColor="#000000" android:pathData="M11.9,21.1a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path android:fillColor="#000000" android:pathData="M11.9,29a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path android:fillColor="#000000" android:pathData="M11.9,36.9a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path android:fillColor="#000000" android:pathData="M29.7,36.9a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
<path android:fillColor="#000000" android:pathData="M40,6h-2V2h-4v4H14V2h-4v4H8c-2.2,0 -4,1.8 -4,4v32c0,2.2 1.8,4 4,4h32c2.2,0 4,-1.8 4,-4V10C44,7.8 42.2,6 40,6zM40,42H8V16h32V42z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_circular_progress_bar.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M480,905Q390,905 312,872Q235,840 178,782Q120,725 88,648Q55,570 55,480Q55,390 88,313Q120,235 177,178Q235,120 312,87Q390,54 480,54L480,149Q342,149 245,245Q149,342 149,480Q149,618 245,715Q342,811 480,811Q618,811 715,715Q811,618 811,480L906,480Q906,570 873,648Q840,725 783,783Q725,840 648,873Q570,905 480,905Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_close.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_colors.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M18,4L18,3c0,-0.6 -0.5,-1 -1,-1L5,2c-0.6,0 -1,0.5 -1,1v4c0,0.6 0.5,1 1,1h12c0.6,0 1,-0.5 1,-1L18,6h1v4L9,10v11c0,0.6 0.5,1 1,1h2c0.6,0 1,-0.5 1,-1v-9h8L21,4h-3zM16,6L6,6L6,4h10v2z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_contrast.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text"
android:autoMirrored="true">
<path
android:fillColor="@android:color/black"
android:pathData="M480,899Q393,899 317,866Q240,833 183,777Q127,720 94,643Q61,567 61,480Q61,393 94,317Q127,240 183,183Q240,127 317,94Q393,61 480,61Q567,61 643,94Q720,127 777,183Q833,240 866,317Q899,393 899,480Q899,567 866,643Q833,720 777,777Q720,833 643,866Q567,899 480,899ZM527,789Q641,772 717,686Q793,600 793,480Q793,361 717,275Q641,189 527,172L527,789Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_download.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M280,680H680V600H280ZM480,560L640,400L584,344L520,406V240H440V406L376,344L320,400ZM480,880Q397,880,324,848.5Q251,817,197,763Q143,709,111.5,636Q80,563,80,480Q80,397,111.5,324Q143,251,197,197Q251,143,324,111.5Q397,80,480,80Q563,80,636,111.5Q709,143,763,197Q817,251,848.5,324Q880,397,880,480Q880,563,848.5,636Q817,709,763,763Q709,817,636,848.5Q563,880,480,880ZM480,800Q614,800,707,707Q800,614,800,480Q800,346,707,253Q614,160,480,160Q346,160,253,253Q160,346,160,480Q160,614,253,707Q346,800,480,800Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_download_filled.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M280,680H680V600H280ZM480,560L640,400L584,344L520,406V240H440V406L376,344L320,400ZM480,880Q397,880,324,848.5Q251,817,197,763Q143,709,111.5,636Q80,563,80,480Q80,397,111.5,324Q143,251,197,197Q251,143,324,111.5Q397,80,480,80Q563,80,636,111.5Q709,143,763,197Q817,251,848.5,324Q880,397,880,480Q880,563,848.5,636Q817,709,763,763Q709,817,636,848.5Q563,880,480,880Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_fullscreen.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M120,840L120,640L200,640L200,760L320,760L320,840L120,840ZM640,840L640,760L760,760L760,640L840,640L840,840L640,840ZM120,320L120,120L320,120L320,200L200,200L200,320L120,320ZM760,320L760,200L640,200L640,120L840,120L840,320L760,320Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_height.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="30dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M480,854L298,672L354,617L440,702L439,258L354,344L297,288L480,105L663,288L606,344L520,258L520,702L607,617L663,672L480,854Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_horizontal.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M280,840L80,640L280,440L337,496L233,600L840,600L840,680L233,680L337,784L280,840ZM680,520L623,464L727,360L120,360L120,280L727,280L623,176L680,120L880,320L680,520Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_info.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/black"
android:pathData="M440,680L520,680L520,440L440,440L440,680ZM480,360Q497,360 509,349Q520,337 520,320Q520,303 509,292Q497,280 480,280Q463,280 452,292Q440,303 440,320Q440,337 452,349Q463,360 480,360ZM480,880Q397,880 324,849Q251,817 197,763Q143,709 112,636Q80,563 80,480Q80,397 112,324Q143,251 197,197Q251,143 324,112Q397,80 480,80Q563,80 636,112Q709,143 763,197Q817,251 849,324Q880,397 880,480Q880,563 849,636Q817,709 763,763Q709,817 636,849Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_launcher_foreground.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="480"
android:viewportHeight="480"
android:tint="@color/deep_purple_800">
<group android:scaleX="0.72"
android:scaleY="0.72"
android:translateX="67.2"
android:translateY="67.2">
<path
android:pathData="M290.5,119.3c-25.8,0 -46.7,20.9 -46.7,46.7c0,25.8 20.9,46.7 46.7,46.7c25.8,0 46.7,-20.9 46.7,-46.7C337.2,140.2 316.3,119.3 290.5,119.3zM290.5,196.3c-16.7,0 -30.3,-13.6 -30.3,-30.3c0,-16.7 13.6,-30.3 30.3,-30.3s30.3,13.6 30.3,30.3C320.8,182.7 307.3,196.3 290.5,196.3z"
android:fillColor="@android:color/black"/>
<path
android:pathData="M142.7,122.6H158v11.8h0.7c2.4,-4.2 6.2,-7.6 11.2,-10.4c5,-2.8 10.3,-4.2 15.7,-4.2c10.4,0 18.4,3 24,8.9c5.6,6 8.4,14.4 8.4,25.4v53.5h-16v-52.4c-0.3,-13.9 -7.4,-20.8 -21,-20.8c-6.4,0 -11.7,2.6 -16,7.7c-4.3,5.2 -6.4,11.3 -6.4,18.5v47.1h-16V122.6z"
android:fillColor="@android:color/black"/>
<path
android:pathData="M186.9,360.6c-7,0 -12.9,-2.2 -17.5,-6.5c-4.6,-4.3 -7,-10.4 -7.1,-18.1v-48.2h-15.1v-14.8h15.1v-26.4h16.2v26.4h21.1v14.8h-21.1v42.9c0,5.8 1.1,9.7 3.3,11.7c2.2,2.1 4.8,3.1 7.6,3.1c1.3,0 2.6,-0.1 3.8,-0.4c1.2,-0.3 2.4,-0.7 3.4,-1.1l5.1,14.4C197.4,359.8 192.5,360.6 186.9,360.6z"
android:fillColor="@android:color/black"/>
<path
android:pathData="M288,253.3a12.4,12.4 0,1 1,24.8 0a12.4,12.4 0,1 1,-24.8 0"
android:fillColor="@android:color/black"/>
<path
android:pathData="M308.3,361.4h-16c-0.6,0 -1,-0.4 -1,-1v-77.7c0,-0.6 0.4,-1 1,-1h16c0.6,0 1,0.4 1,1v77.7C309.3,361 308.9,361.4 308.3,361.4z"
android:fillColor="@android:color/black"/>
</group>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_linear_progress_bar.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M66,589L66,510L288,510L288,589L66,589ZM348,589L348,510L573,510L573,589L348,589ZM633,589L633,510L855,510L855,589L633,589ZM66,450L66,370L855,370L855,450L66,450Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_lockscreen.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M19,1L9,1c-1.1,0 -2,0.9 -2,2v3h2L9,4h10v16L9,20v-2L7,18v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L21,3c0,-1.1 -0.9,-2 -2,-2zM10.8,11L10.8,9.5C10.8,8.1 9.4,7 8,7S5.2,8.1 5.2,9.5L5.2,11c-0.6,0 -1.2,0.6 -1.2,1.2v3.5c0,0.7 0.6,1.3 1.2,1.3h5.5c0.7,0 1.3,-0.6 1.3,-1.2v-3.5c0,-0.7 -0.6,-1.3 -1.2,-1.3zM9.5,11h-3L6.5,9.5c0,-0.8 0.7,-1.3 1.5,-1.3s1.5,0.5 1.5,1.3L9.5,11z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_margin_top.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M440,643L520,643L520,468L590,539L646,483L480,317L314,483L370,539L440,468L440,643ZM480,894Q395,894 319,862Q244,829 187,773Q131,717 98,641Q66,566 66,480Q66,394 98,318Q131,243 187,187Q243,131 319,98Q394,65 480,65Q566,65 642,98Q718,131 774,186Q830,242 862,318Q895,393 895,480Q895,566 862,641Q829,717 773,773Q718,829 642,862Q567,894 480,894ZM480,815Q620,815 717,717Q815,620 815,480Q815,340 717,243Q620,145 480,145Q341,145 243,243Q145,340 145,480Q145,619 243,717Q341,815 480,815Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_music.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M12,3l0.01,10.55c-0.59-0.34-1.27-0.55-2-0.55C7.79,13,6,14.79,6,17s1.79,4,4.01,4S14,19.21,14,17V7h4V3zM10.01,19c-1.1,0-2-0.9-2-2s0.9-2,2-2s2,0.9,2,2-0.9,2-2,2z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_music_filled.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M12,3v10.55c-0.59-0.34-1.27-0.55-2-0.55-2.21,0-4,1.79-4,4s1.79,4,4,4s4-1.79,4-4V7h4V3z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_notification.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="27dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M91,404Q91,306 134,225Q176,143 250,89L295,150Q236,193 201,259Q166,325 166,404L91,404ZM795,404Q795,325 759,259Q724,193 666,150L711,89Q784,144 827,225Q870,306 870,404L795,404ZM146,774L146,695L221,695L221,405Q221,316 272,245Q324,173 410,154L410,136Q410,106 431,86Q451,65 480,65Q509,65 529,86Q550,106 550,136L550,154Q636,172 688,244Q740,316 740,405L740,695L815,695L815,774L146,774ZM480,899Q445,899 420,874Q396,849 396,814L565,814Q565,849 540,874Q515,899 480,899ZM300,695L660,695L660,405Q660,331 608,278Q555,225 480,225Q405,225 353,278Q300,331 300,405L300,695Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_notification_bar.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="27dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M120,840L120,510L840,510L840,840L120,840ZM120,450L120,120L840,120L840,450L120,450Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_overlay.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M479,868L96,569L162,520L479,768L798,520L864,569ZM479,692L96,394L479,95L863,394ZM479,591L733,394L479,196L226,394Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_palette.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M480,880Q398,880,325,848.5Q252,817,197.5,762.5Q143,708,111.5,635Q80,562,80,480Q80,397,112.5,324Q145,251,200.5,197Q256,143,330,111.5Q404,80,488,80Q568,80,639,107.5Q710,135,763.5,183.5Q817,232,848.5,298.5Q880,365,880,442Q880,557,810,618.5Q740,680,640,680H566Q557,680,553.5,685Q550,690,550,696Q550,708,565,730.5Q580,753,580,782Q580,832,552.5,856Q525,880,480,880ZM260,520Q286,520,303,503Q320,486,320,460Q320,434,303,417Q286,400,260,400Q234,400,217,417Q200,434,200,460Q200,486,217,503Q234,520,260,520ZM380,360Q406,360,423,343Q440,326,440,300Q440,274,423,257Q406,240,380,240Q354,240,337,257Q320,274,320,300Q320,326,337,343Q354,360,380,360ZM580,360Q606,360,623,343Q640,326,640,300Q640,274,623,257Q606,240,580,240Q554,240,537,257Q520,274,520,300Q520,326,537,343Q554,360,580,360ZM700,520Q726,520,743,503Q760,486,760,460Q760,434,743,417Q726,400,700,400Q674,400,657,417Q640,434,640,460Q640,486,657,503Q674,520,700,520ZM480,800Q489,800,494.5,795Q500,790,500,782Q500,768,485,749Q470,730,470,692Q470,650,499,625Q528,600,570,600H640Q706,600,753,561.5Q800,523,800,442Q800,321,707.5,240.5Q615,160,488,160Q352,160,256,253Q160,346,160,480Q160,613,253.5,706.5Q347,800,480,800Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_preview.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M19.8,5.7c0.6,0.7 1.1,1.5 1.5,2.4S21.9,10 22,11h-2c-0.1,-0.7 -0.3,-1.4 -0.6,-2.1c-0.3,-0.7 -0.6,-1.3 -1,-1.8L19.8,5.7zM22,13c-0.1,1 -0.4,1.9 -0.8,2.8c-0.4,0.9 -0.9,1.7 -1.5,2.4l-1.4,-1.4c0.4,-0.5 0.8,-1.2 1,-1.8c0.3,-0.7 0.5,-1.4 0.6,-2.1H22zM13.1,2c1,0.1 1.9,0.3 2.8,0.7c0.9,0.4 1.7,0.9 2.5,1.5L17,5.7c-0.6,-0.4 -1.2,-0.8 -1.8,-1.1c-0.6,-0.3 -1.3,-0.5 -2.1,-0.6V2zM16.9,18.3l1.5,1.5c-0.8,0.6 -1.6,1.1 -2.5,1.5C15,21.6 14,21.9 13,22v-2c0.8,-0.1 1.4,-0.3 2.1,-0.6C15.8,19.1 16.4,18.7 16.9,18.3zM9.5,16.5v-9l7,4.5L9.5,16.5zM11,2v2C9,4.3 7.3,5.2 6,6.7S4,10 4,12s0.7,3.8 2,5.3s3,2.4 5,2.7v2c-2.6,-0.3 -4.7,-1.4 -6.4,-3.2C2.9,16.8 2,14.6 2,12s0.9,-4.8 2.6,-6.7S8.4,2.3 11,2z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_progress_bar_style.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M190,180L190,100L770,100L770,180L190,180ZM480,859Q357,859 272,774Q187,689 187,566Q187,444 272,359Q358,274 480,274Q603,274 688,359Q773,444 773,567Q773,689 688,774Q602,859 480,859ZM480,754Q557,754 612,699Q667,645 667,567Q667,489 613,434Q558,379 480,379Q403,379 348,434Q293,488 293,566Q293,644 347,699Q402,754 480,754Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_rounded_corners.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M95,865L95,783L177,783L177,865L95,865ZM95,693L95,610L177,610L177,693L95,693ZM95,521L95,439L177,439L177,521L95,521ZM95,349L95,266L177,266L177,349L95,349ZM95,177L95,94L177,94L177,177L95,177ZM267,865L267,783L350,783L350,865L267,865ZM267,177L267,94L350,94L350,177L267,177ZM439,865L439,783L521,783L521,865L439,865ZM611,865L611,783L694,783L694,865L611,865ZM783,865L783,783L866,783L866,865L783,865ZM783,693L783,610L866,610L866,693L783,693ZM866,521L783,521L783,307Q783,254 745,215Q707,177 653,177L439,177L439,94L653,94Q742,94 804,156Q866,218 866,307L866,521Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_share.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M720,880Q670,880 635,845Q600,810 600,760Q600,753 601,746Q602,738 604,732L322,568Q305,583 284,592Q263,600 240,600Q190,600 155,565Q120,530 120,480Q120,430 155,395Q190,360 240,360Q263,360 284,369Q305,377 322,392L604,228Q602,222 601,215Q600,207 600,200Q600,150 635,115Q670,80 720,80Q770,80 805,115Q840,150 840,200Q840,250 805,285Q770,320 720,320Q697,320 676,312Q655,303 638,288L356,452Q358,458 359,466Q360,473 360,480Q360,487 359,495Q358,502 356,508L638,672Q655,657 676,649Q697,640 720,640Q770,640 805,675Q840,710 840,760Q840,810 805,845Q770,880 720,880Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_size.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="@color/text">
<path
android:fillColor="@android:color/black"
android:pathData="M106,854L106,598L185,598L185,718L324,580L380,637L242,775L362,775L362,854L106,854ZM598,854L598,775L718,775L581,638L638,581L775,718L775,598L855,598L855,854L598,854ZM323,379L185,242L185,362L106,362L106,105L362,105L362,185L242,185L379,323L323,379ZM638,379L581,323L718,185L598,185L598,105L855,105L855,362L775,362L775,242L638,379Z"/>
</vector>
================================================
FILE: app/src/main/res/drawable/ic_sumup.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="28dp"
android:height="28dp"
android:viewportWidth="921.6"
android:viewportHeight="921.6"
android:tint="@color/text">
<group android:scaleX="1.3"
android:scaleY="1.3"
android:translateX="-138.24"
android:translateY="-138.24">
<path
android:pathData="M680.8,699h-440c-27.6,0-50-22.4-50-50V532.4c0-27.6,22.4-50,50-50h440c27.6,0,50,22.4,50,50V649C730.8,676.6,708.4,699,680.8,699z"
android:fillColor="@android:color/black"/>
<path
android:pathData="M700.5,353.3c-18.1,0-34.1-10.2-41.7-26.7l-20.6-45.4L593,260.7c-15.5-7-25.5-21.4-26.8-38.1H240.8c-27.6,0-50,22.4-50,50v116.6c0,27.6,22.4,50,50,50h440c27.6,0,50-22.4,50-50v-47.4C722.6,349.1,711.9,353.3,700.5,353.3z"
android:fillColor="@android:color/black"/>
<path
android:pathData="M795.5,204.6l-55.5-25.2-25.2-55.5c-5.5-12.4-23.3-12.4-28.8,0l-25.2,55.5-55.5,25.2c-12.4,5.7-12.4,23.3,0,28.8l55.5,25.2L686,314c5.7,12.4,23.3,12.4,28.8,0l25.2-55.5l55.5-25.2C807.9,227.7,807.9,210.1,795.5,204.6z"
android:fillColor="@android:color/black"/>
</group>
</vector>
================================================
FILE: app/src/main/res/drawable/layout_bg.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="12dp" />
<solid android:color="?attr/colorBackgroundFloating" />
</shape>
================================================
FILE: app/src/main/res/drawable/selector_download.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_download_filled" android:state_checked="true"/>
<item android:drawable="@drawable/ic_download"/>
</selector>
================================================
FILE: app/src/main/res/drawable/selector_music.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_music_filled" android:state_checked="true"/>
<item android:drawable="@drawable/ic_music"/>
</selector>
================================================
FILE: app/src/main/res/drawable/splashscreen.xml
================================================
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30"
android:tint="@color/deep_purple_800">
<group
android:name="splashscreen_group"
android:translateX="3"
android:translateY="3">
<path
android:fillColor="@android:color/black"
android:pathData="M3.4,10.5h1v0.7h0c0.2,-0.3 0.4,-0.5 0.7,-0.7c0.3,-0.2 0.7,-0.3 1,-0.3c0.7,0 1.2,0.2 1.5,0.6c0.4,0.4 0.5,0.9 0.5,1.6v3.4h-1v-3.3c0,-0.9 -0.5,-1.3 -1.3,-1.3c-0.4,0 -0.7,0.2 -1,0.5c-0.3,0.3 -0.4,0.7 -0.4,1.2v3h-1V10.5z"/>
<path
android:fillColor="@android:color/black"
android:pathData="M11.8,10.3c-1.6,0 -2.9,1.3 -2.9,2.9c0,1.6 1.3,2.9 2.9,2.9c1.6,0 2.9,-1.3 2.9,-2.9C14.7,11.6 13.4,10.3 11.8,10.3zM11.8,15.2c-1.1,0 -2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2c1.1,0 2,0.9 2,2C13.8,14.3 12.9,15.2 11.8,15.2z"/>
<path
android:fillColor="@android:color/black"
android:pathData="M17.5,16c-0.4,0 -0.8,-0.1 -1.1,-0.4s-0.4,-0.6 -0.4,-1.1v-3H15v-0.9h0.9V8.9h1v1.6h1.3v0.9H17v2.7c0,0.4 0.1,0.6 0.2,0.7c0.1,0.1 0.3,0.2 0.5,0.2c0.1,0 0.2,0 0.2,0s0.1,0 0.2,-0.1l0.3,0.9C18.1,15.9 17.8,16 17.5,16z"/>
<path
android:fillColor="@android:color/black"
android:pathData="M20.6,8.6c0,0.2 -0.1,0.4 -0.2,0.5c-0.1,0.1 -0.3,0.2 -0.5,0.2s-0.4,-0.1 -0.5,-0.2c-0.1,-0.1 -0.2,-0.3 -0.2,-0.5c0,-0.2 0.1,-0.4 0.2,-0.5c0.1,-0.1 0.3,-0.2 0.5,-0.2s0.4,0.1 0.5,0.2S20.6,8.4 20.6,8.6zM20.4,10.5v5.4h-1v-5.4H20.4z"/>
</group>
</vector>
================================================
FILE: app/src/main/res/drawable-v31/splashscreen.xml
================================================
<animated-vector
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt">
<aapt:attr name="android:drawable">
<vector
android:name="noti_splashscreen"
android:width="30dp"
android:height="30dp"
android:viewportWidth="30"
android:viewportHeight="30"
android:tint="@color/deep_purple_800">
<group
android:name="pb_group"
android:translateX="14.85"
android:translateY="22.2"
android:scaleX="0.45"
android:scaleY="0.45"
android:rotation="-135">
<path
android:name="progress_bar"
android:pathData="M 13.4 13.4 C 15.5 11.2 15.5 7.8 13.4 5.6 C 11.2 3.5 7.8 3.5 5.6 5.6 C 3.5 7.8 3.5 11.2 5.6 13.4 C 7.8 15.5 11.2 15.5 13.4 13.4 Z"
android:strokeColor="@android:color/black"
android:strokeWidth="2.1"/>
</group>
<group
android:name="letters_group"
android:translateX="3"
android:translateY="3">
<path
android:name="letters"
android:pathData="M 3.4 10.5 L 4.4 10.5 L 4.4 11.2 C 4.6 10.9 4.8 10.7 5.1 10.5 C 5.4 10.3 5.8 10.2 6.1 10.2 C 6.8 10.2 7.3 10.4 7.6 10.8 C 8 11.2 8.1 11.7 8.1 12.4 L 8.1 15.8 L 7.1 15.8 L 7.1 12.5 C 7.1 11.6 6.6 11.2 5.8 11.2 C 5.4 11.2 5.1 11.4 4.8 11.7 C 4.5 12 4.4 12.4 4.4 12.9 L 4.4 15.9 L 3.4 15.9 L 3.4 10.5 Z M 17.5 16 C 17.1 16 16.7 15.9 16.4 15.6 C 16.1 15.3 16 15 16 14.5 L 16 11.5 L 15 11.5 L 15 10.6 L 15.9 10.6 L 15.9 8.9 L 16.9 8.9 L 16.9 10.5 L 18.2 10.5 L 18.2 11.4 L 17 11.4 L 17 14.1 C 17 14.5 17.1 14.7 17.2 14.8 C 17.3 14.9 17.5 15 17.7 15 L 17.9 15 C 17.9 15 18 15 18.1 14.9 L 18.4 15.8 C 18.1 15.9 17.8 16 17.5 16 Z M 20.6 8.6 C 20.6 8.8 20.5 9 20.4 9.1 C 20.3 9.2 20.1 9.3 19.9 9.3 C 19.7 9.3 19.5 9.2 19.4 9.1 C 19.3 9 19.2 8.8 19.2 8.6 C 19.2 8.4 19.3 8.2 19.4 8.1 C 19.5 8 19.7 7.9 19.9 7.9 C 20.1 7.9 20.3 8 20.4 8.1 C 20.5 8.2 20.6 8.4 20.6 8.6 Z M 20.4 10.5 L 20.4 15.9 L 19.4 15.9 L 19.4 10.5 L 20.4 10.5 Z"
android:fillColor="@android:color/black"
android:fillAlpha="0"/>
</group>
</vector>
</aapt:attr>
<target android:name="progress_bar">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="trimPathStart"
android:startOffset="25"
android:duration="400"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"/>
</aapt:attr>
</target>
<target android:name="letters">
<aapt:attr name="android:animation">
<objectAnimator
android:propertyName="fillAlpha"
android:startOffset="225"
android:duration="200"
android:valueFrom="0"
android:valueTo="1"
android:valueType="floatType"
android:interpolator="@android:anim/decelerate_interpolator"/>
</aapt:attr>
</target>
</animated-vector>
================================================
FILE: app/src/main/res/layout/activity_main.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
style="?attr/collapsingToolbarLayoutLargeStyle"
android:id="@+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
app:collapsedTitleGravity="center"
android:background="?android:attr/colorBackground"
app:contentScrim="?android:attr/colorBackground"
app:expandedTitleMarginStart="30dp"
app:expandedTitleMarginBottom="28dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:title="@string/app_name"
app:menu="@menu/settings_menu" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/previewFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_gravity="bottom|end"
app:icon="@drawable/ic_preview"
android:text="@string/settingsPreviewFab"
android:textSize="18sp"
app:iconSize="26dp"
android:layout_marginEnd="30dp"
android:layout_marginBottom="30dp" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
================================================
FILE: app/src/main/res/layout/advanced_style_dialog.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
style="?android:attr/windowTitleStyle"
android:id="@+id/portraitStyleTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/prefsProgressBarStylePortraitTitle"
android:singleLine="false"
android:paddingStart="?dialogPreferredPadding"
android:paddingEnd="?dialogPreferredPadding"
android:paddingTop="18dp"
android:paddingBottom="8dp" />
<ListView
android:id="@+id/portraitStyleOptions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@null"
android:choiceMode="singleChoice" />
<TextView
style="?android:attr/windowTitleStyle"
android:id="@+id/landscapeStyleTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/prefsProgressBarStyleLandscapeTitle"
android:singleLine="false"
android:paddingStart="?dialogPreferredPadding"
android:paddingEnd="?dialogPreferredPadding"
android:paddingTop="8dp"
android:paddingBottom="8dp"/>
<ListView
android:id="@+id/landscapeStyleOptions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="@null"
android:choiceMode="singleChoice"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
================================================
FILE: app/src/main/res/layout/app_item.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/item_container"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:paddingVertical="10dp"
android:background="?android:attr/selectableItemBackground">
<TextView
android:id="@+id/app_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="14dp"
android:layout_marginEnd="12dp"
android:drawablePadding="19dp"
android:gravity="center_vertical"
android:textSize="22sp"
android:textColor="?android:attr/textColorPrimary"
android:layout_weight="1"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/color_picker"
android:layout_width="41dp"
android:layout_height="41dp"
android:insetTop="5dp"
android:insetBottom="5dp"
android:insetLeft="5dp"
android:insetRight="5dp"
android:scrollbars="none"
android:foreground="?actionBarItemBackground"
app:strokeColor="#46000000"
app:strokeWidth="2dp"/>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/banner_preference.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/Widget.Material3.CardView.Filled"
app:cardCornerRadius="18dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="?android:attr/listPreferredItemPaddingStart">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="8dp"
android:layout_marginStart="24dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@android:id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginTop="16dp"
android:textAppearance="?android:attr/textAppearanceListItem" />
<ImageButton
android:id="@+id/closeBtn"
android:layout_width="38dp"
android:layout_height="38dp"
android:padding="6dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="6dp"
android:background="@drawable/circular_mask"
android:scaleType="fitCenter"
android:src="@drawable/ic_close"
app:tint="@color/text" />
</LinearLayout>
<Button
android:id="@+id/bannerBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.Material3.Button.TextButton"
android:layout_marginTop="4dp"
android:textColor="?attr/colorOnPrimaryContainer"/>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
================================================
FILE: app/src/main/res/layout/button_toggle_group.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="?android:attr/listPreferredItemPaddingStart">
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
app:cardCornerRadius="18dp"
android:layout_width="0dp"
android:layout_height="match_parent"
app:cardBackgroundColor="?attr/colorBackgroundFloating"
app:layout_constraintWidth_max="400dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingVertical="4dp"
android:paddingHorizontal="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/activateToggle"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginTop="4dp"
android:layout_marginHorizontal="14dp" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/toggleGroup"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/toggleDownloads"
style="@style/ToggleGroupButtonStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
app:icon="@drawable/selector_download"
app:iconSize="28dp"
app:iconGravity="top"
android:text="@string/downloads"
android:paddingVertical="16dp"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/toggleMedia"
style="@style/ToggleGroupButtonStyle"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
app:icon="@drawable/selector_music"
app:iconSize="28dp"
app:iconGravity="top"
android:text="@string/media"
android:paddingVertical="16dp"/>
</com.google.android.material.button.MaterialButtonToggleGroup>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/fragment_per_app_settings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/apps_recycler_view"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:paddingBottom="100dp"
android:scrollbars="none"
android:visibility="gone"
android:clipToPadding="false"/>
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="30dp"
android:gravity="center_horizontal"
android:text="@string/perAppSettingsEmptyView"
android:textColor="?android:attr/textColorPrimary"
android:textSize="20sp"
android:layout_marginBottom="100dp"/>
</LinearLayout>
================================================
FILE: app/src/main/res/layout/material_switch.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<!-- https://stackoverflow.com/a/76270479 -->
<com.google.android.material.materialswitch.MaterialSwitch
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/switchWidget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null" />
================================================
FILE: app/src/main/res/layout/per_app_settings_footer.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="14dp"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="4"
android:ellipsize="end"
android:gravity="center_vertical"
android:text="@string/perAppSettingsEmptyView"
android:layout_marginVertical="6dp"
android:lineSpacingMultiplier="1.2"
android:letterSpacing="0.018"
android:paddingVertical="10dp"
app:drawableLeftCompat="@drawable/ic_info"
android:drawablePadding="28dp"/>
================================================
FILE: app/src/main/res/layout/progress_bar.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container_parent"
android:layout_height="match_parent"
android:layout_width="match_parent">
<FrameLayout
android:id="@+id/container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="@integer/progress_bar_max"
android:progress="0"
android:theme="@style/Theme.NotiProgressBar"
android:visibility="gone"
app:trackStopIndicatorSize="0dp"
app:indicatorTrackGapSize="0dp"
app:hideAnimationBehavior="outward" />
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/circularProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:max="@integer/progress_bar_max"
android:progress="0"
android:theme="@style/Theme.NotiProgressBar"
android:visibility="visible"
app:hideAnimationBehavior="inward"
app:indicatorInset="0dp"
app:indicatorTrackGapSize="0dp"
app:showAnimationBehavior="outward"/>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/layout/seekbar_preference.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:clipChildren="false"
android:clipToPadding="false"
android:baselineAligned="false">
<ImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginStart="12dp"
android:layout_marginTop="6dp"
android:layout_marginBottom="4dp">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceListItem"
android:ellipsize="marquee"
android:layout_marginStart="16dp"
android:fadingEdge="horizontal"/>
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="4"/>
<com.google.android.material.slider.Slider
android:id="@+id/slider"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:labelBehavior="gone"
android:paddingTop="4dp"
android:stepSize="1"/>
</LinearLayout>
</LinearLayout>
================================================
FILE: app/src/main/res/layout-land/button_toggle_group.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="28dp"
android:layout_marginVertical="?android:attr/listPreferredItemPaddingStart">
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Filled"
app:cardCornerRadius="18dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:cardBackgroundColor="?attr/colorBackgroundFloating"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:paddingVertical="4dp"
android:paddingHorizontal="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/activateToggle"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginHorizontal="14dp"/>
<com.google.android.material.button.MaterialButtonToggleGroup
android:id="@+id/toggleGroup"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/toggleDownloads"
style="@style/ToggleGroupButtonStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingVertical="8dp"
android:paddingHorizontal="48dp"
app:icon="@drawable/selector_download"
app:iconSize="28dp"
app:iconPadding="10dp"
android:text="@string/downloads"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/toggleMedia"
style="@style/ToggleGroupButtonStyle"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:paddingVertical="8dp"
android:paddingHorizontal="48dp"
app:icon="@drawable/selector_music"
app:iconSize="28dp"
app:iconPadding="10dp"
android:text="@string/media"/>
</com.google.android.material.button.MaterialButtonToggleGroup>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
================================================
FILE: app/src/main/res/menu/settings_menu.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/bug_report"
android:icon="@drawable/ic_bug_report"
android:title="@string/menuReportBug"
app:showAsAction="ifRoom"
android:visible="false"/>
</menu>
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
================================================
FILE: app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
================================================
FILE: app/src/main/res/resources.properties
================================================
unqualifiedResLocale=en
================================================
FILE: app/src/main/res/values/arrays.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="progressBarStyle">
<item>@string/prefsProgressBarStyleEntryNone</item>
<item>@string/prefsProgressBarStyleEntryCircular</item>
<item>@string/prefsProgressBarStyleEntryLinear</item>
</string-array>
<string-array name="progressBarStyleValues">
<item>none</item>
<item>circular</item>
<item>linear</item>
</string-array>
<string-array name="colorsArray">
<item>@string/colorRed</item>
<item>@string/colorPink</item>
<item>@string/colorPurple</item>
<item>@string/colorDeepPurple</item>
<item>@string/colorIndigo</item>
<item>@string/colorBlue</item>
<item>@string/colorLightBlue</item>
<item>@string/colorCyan</item>
<item>@string/colorTeal</item>
<item>@string/colorGreen</item>
<item>@string/colorLightGreen</item>
<item>@string/colorLime</item>
<item>@string/colorYellow</item>
<item>@string/colorAmber</item>
<item>@string/colorOrange</item>
<item>@string/colorDeepOrange</item>
<item>@string/colorBrown</item>
<item>@string/colorGrey</item>
<item>@string/colorWhite</item>
</string-array>
<array name="colorsArrayValues">
<item>#D32F2F</item>
<item>#D81B60</item>
<item>#8E24AA</item>
<item>#6200EE</item>
<item>#303F9F</item>
<item>#1A73E9</item>
<item>#03A9F4</item>
<item>#00BCD4</item>
<item>#009688</item>
<item>#4CAF50</item>
<item>#8BC34A</item>
<item>#CDDC39</item>
<item>#FFEB3B</item>
<item>#FFC107</item>
<item>#FF9800</item>
<item>#FF5722</item>
<item>#795548</item>
<item>#9E9E9E</item>
<item>#FFFFFF</item>
</array>
</resources>
================================================
FILE: app/src/main/res/values/attrs.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="HorizontalNumberPicker">
<attr name="min_number" format="integer"/>
<attr name="max_number" format="integer"/>
<attr name="default_value" format="integer"/>
</declare-styleable>
</resources>
================================================
FILE: app/src/main/res/values/colors.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_500">#FF6200EE</color>
<color name="deep_purple_100">#D1C4E9</color>
<color name="deep_purple_800">#4527A0</color>
<color name="text">#000000</color>
<color name="system_accent_color">@color/material_dynamic_primary40</color>
<color name="app_accent_color">#EADDFF</color>
</resources>
================================================
FILE: app/src/main/res/values/dimens.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="progress_bar_max">10000</integer>
</resources>
================================================
FILE: app/src/main/res/values/ic_launcher_background.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">@color/deep_purple_100</color>
</resources>
================================================
FILE: app/src/main/res/values/strings.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Noti Progress Bar</string>
<string name="app_name_short" translatable="false">noti</string>
<string name="menuReportBug">Report Bug</string>
<string name="settingsPreviewFab">Preview</string>
<string name="notificationListenerDescription">Reads notifications with a progress bar</string>
<string name="perAppSettingsEmptyView">Noti will identify more compatible apps as notifications are received</string>
<string name="activateToggle">Enabled for</string>
<string name="downloads">Downloads</string>
<string name="media">Media</string>
<string name="reset">Reset</string>
<string name="holePunchInstruction">Change alignment in circular progress bar tweaks</string>
<string name="prefsBatteryBannerSummary">You may need to disable battery optimizations and allow autostart for Noti to work in the background. A reboot after doing this may be necessary</string>
<string name="prefsBatteryBannerButton">Learn more</string>
<string name="prefsCategorySetup">setup</string>
<string name="prefsCategoryGeneral">general</string>
<string name="prefsNotificationPermissionTitle">Allow notification access</string>
<string name="prefsNotificationPermissionSummary">Required to read notifications with a progress bar</string>
<string name="prefsAccessibilityPermissionTitle">Grant accessibility permission</string>
<string name="prefsAccessibilityPermissionSummary">Required to display in the lock screen and system apps</string>
<string name="prefsSAWPermissionTitle">Allow displaying over other apps</string>
<string name="prefsSAWPermissionSummary">Required to display the progress bar overlay</string>
<string name="prefsCategoryCustomization">customization</string>
<string name="prefsProgressBarStyleTitle">Select progress bar style</string>
<string name="prefsProgressBarStylePortraitTitle">Portrait progress bar style</string>
<string name="prefsProgressBarStyleLandscapeTitle">Landscape progress bar style</string>
<string name="advancedOptions">Advanced Options</string>
<string name="prefsProgressBarStyleEntryNone">No progress bar</string>
<string name="prefsProgressBarStyleEntryLinear">Linear progress bar</string>
<string name="prefsProgressBarStyleEntryCircular">Circular progress bar</string>
<string name="prefsCategoryTweaks">tweaks</string>
<string name="prefsLinearProgressBarTweaks">Linear progress bar tweaks</string>
<string name="prefsProgressBarThicknessTitle">Progress bar thickness</string>
<string name="prefsProgressBarThicknessSummary">Change size of the progress bar\'s track</string>
<string name="prefsShowBelowNotchTitle">Show below display cutout</string>
<string name="prefsShowBelowNotchSummary">Display progress bar below any notch or hole punch</string>
<string name="prefsStatusBarHeightInfo">Your status bar is %d pixels tall</string>
<string name="prefsCircularProgressBarTweaks">Circular progress bar tweaks</string>
<string name="prefsProgressBarOffsetTitle">Horizontal offset</string>
<string name="prefsProgressBarOffsetSummary">Change progress bar alignment</string>
<string name="prefsCutoutSizeTitle">Cutout size</string>
<string name="prefsCutoutSizeSummary">Change size of the inner diameter</string>
<string name="prefsProgressBarMarginTopTitle">Margin from the top</string>
<string name="prefsProgressBarMarginTopSummary">Change spacing from top of the screen</string>
<string name="prefsCircularBarShareConfigTitle">Perfect fit? Share configuration</string>
<string name="prefsCircularBarShareConfigSummary">Let others with the same device use your configuration</string>
<string name="prefsMultiScreenInfoCard">For multiple screens, access the settings through each display to adjust the bar for that setup</string>
<string name="prefsBlackBackgroundTitle">Use black background</string>
<string name="prefsBlackBackgroundSummary">Switch to progress bar with black track</string>
<string name="prefsPerAppSettingsTitle">Per-app settings</string>
<string name="prefsPerAppSettingsSummary">Toggle noti on per-app basis</string>
<string name="prefsRoundedCornersTitle">Use progress bar with rounded corners</string>
<string name="prefsRoundedCornersSummary">Switch to progress bar with rounded ends</string>
<string name="prefsShowInLockScreenTitle">Show progress bar in lock screen</string>
<string name="prefsShowInLockScreenSummary">Will also show in the always on display</string>
<string name="prefsDisableInFullScreenTitle">Disable in fullscreen mode</string>
<string name="prefsDisableInFullScreenSummary">Hide progress bar in fullscreen apps</string>
<string name="prefsNotificationColorTitle">Match notification color</string>
<string name="prefsNotificationColorSummary">Use the notification\'s color for the progress bar</string>
<string name="prefsChangeColorTitle">Change progress bar color</string>
<string name="prefsCategoryMore">more</string>
<string name="prefsCalentileTitle">Try %s on Google Play</string>
<string name="prefsCalentileSummary">Support the developer</string>
<string name="shareConfigPositiveMessage">Thanks for sharing your configuration!</string>
<string name="shareConfigNoInternetMessage">Enable internet to upload configuration</string>
<string name="colorRed">Red</string>
<string name="colorPink">Pink</string>
<string name="colorPurple">Purple</string>
<string name="colorDeepPurple">Deep purple</string>
<string name="colorIndigo">Indigo</string>
<string name="colorBlue">Blue</string>
<string name="colorLightBlue">Light blue</string>
<string name="colorCyan">Cyan</string>
<string name="colorTeal">Teal</string>
<string name="colorGreen">Green</string>
<string name="colorLightGreen">Light green</string>
<string name="colorLime">Lime</string>
<string name="colorYellow">Yellow</string>
<string name="colorAmber">Amber</string>
<string name="colorOrange">Orange</string>
<string name="colorDeepOrange">Deep orange</string>
<string name="colorBrown">Brown</string>
<string name="colorBlack">Black</string>
<string name="colorGrey">Grey</string>
<string name="colorWhite">White</s
gitextract_91b8wrhl/ ├── .gitignore ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── gustavoas/ │ │ └── noti/ │ │ ├── ProgressBarAppsAdapter.kt │ │ ├── ProgressBarAppsRepository.kt │ │ ├── SettingsActivity.kt │ │ ├── Utils.kt │ │ ├── fragments/ │ │ │ ├── BasePreferenceFragment.kt │ │ │ ├── CircularBarFragment.kt │ │ │ ├── LinearBarFragment.kt │ │ │ ├── PerAppSettingsFragment.kt │ │ │ └── SettingsFragment.kt │ │ ├── model/ │ │ │ ├── DeviceConfiguration.kt │ │ │ ├── ProgressBarApp.kt │ │ │ └── ProgressNotification.kt │ │ ├── notifications/ │ │ │ ├── DownloadProgressBar.kt │ │ │ ├── GoogleTimerProgressBar.kt │ │ │ ├── MediaProgressBar.kt │ │ │ ├── PercentageProgressBar.kt │ │ │ ├── ProgressBarNotification.kt │ │ │ └── TimedProgressBar.kt │ │ ├── preferences/ │ │ │ ├── BannerPreference.kt │ │ │ ├── BarStylesListPreference.kt │ │ │ └── SeekBarPreference.kt │ │ └── services/ │ │ ├── AccessibilityService.kt │ │ ├── FullscreenDetectionService.kt │ │ └── NotificationListenerService.kt │ └── res/ │ ├── color/ │ │ └── outlined_button_selector.xml │ ├── drawable/ │ │ ├── circular_mask.xml │ │ ├── ic_accessibility.xml │ │ ├── ic_apps.xml │ │ ├── ic_bug_report.xml │ │ ├── ic_calentile.xml │ │ ├── ic_circular_progress_bar.xml │ │ ├── ic_close.xml │ │ ├── ic_colors.xml │ │ ├── ic_contrast.xml │ │ ├── ic_download.xml │ │ ├── ic_download_filled.xml │ │ ├── ic_fullscreen.xml │ │ ├── ic_height.xml │ │ ├── ic_horizontal.xml │ │ ├── ic_info.xml │ │ ├── ic_launcher_foreground.xml │ │ ├── ic_linear_progress_bar.xml │ │ ├── ic_lockscreen.xml │ │ ├── ic_margin_top.xml │ │ ├── ic_music.xml │ │ ├── ic_music_filled.xml │ │ ├── ic_notification.xml │ │ ├── ic_notification_bar.xml │ │ ├── ic_overlay.xml │ │ ├── ic_palette.xml │ │ ├── ic_preview.xml │ │ ├── ic_progress_bar_style.xml │ │ ├── ic_rounded_corners.xml │ │ ├── ic_share.xml │ │ ├── ic_size.xml │ │ ├── ic_sumup.xml │ │ ├── layout_bg.xml │ │ ├── selector_download.xml │ │ ├── selector_music.xml │ │ └── splashscreen.xml │ ├── drawable-v31/ │ │ └── splashscreen.xml │ ├── layout/ │ │ ├── activity_main.xml │ │ ├── advanced_style_dialog.xml │ │ ├── app_item.xml │ │ ├── banner_preference.xml │ │ ├── button_toggle_group.xml │ │ ├── fragment_per_app_settings.xml │ │ ├── material_switch.xml │ │ ├── per_app_settings_footer.xml │ │ ├── progress_bar.xml │ │ └── seekbar_preference.xml │ ├── layout-land/ │ │ └── button_toggle_group.xml │ ├── menu/ │ │ └── settings_menu.xml │ ├── mipmap-anydpi-v26/ │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ ├── resources.properties │ ├── values/ │ │ ├── arrays.xml │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── strings.xml │ │ └── themes.xml │ ├── values-ar/ │ │ └── strings.xml │ ├── values-ar-rSA/ │ │ └── strings.xml │ ├── values-bg/ │ │ └── strings.xml │ ├── values-cs/ │ │ └── strings.xml │ ├── values-de/ │ │ └── strings.xml │ ├── values-es/ │ │ └── strings.xml │ ├── values-es-rMX/ │ │ └── strings.xml │ ├── values-fr/ │ │ └── strings.xml │ ├── values-fr-rCA/ │ │ └── strings.xml │ ├── values-hu/ │ │ └── strings.xml │ ├── values-in/ │ │ └── strings.xml │ ├── values-it/ │ │ └── strings.xml │ ├── values-iw/ │ │ └── strings.xml │ ├── values-ja/ │ │ └── strings.xml │ ├── values-ka/ │ │ └── strings.xml │ ├── values-lt/ │ │ └── strings.xml │ ├── values-ml/ │ │ └── strings.xml │ ├── values-nb-rNO/ │ │ └── strings.xml │ ├── values-night/ │ │ ├── colors.xml │ │ └── themes.xml │ ├── values-pl/ │ │ └── strings.xml │ ├── values-pt-rPT/ │ │ └── strings.xml │ ├── values-ro/ │ │ └── strings.xml │ ├── values-ru/ │ │ └── strings.xml │ ├── values-sk/ │ │ └── strings.xml │ ├── values-sl/ │ │ └── strings.xml │ ├── values-ta/ │ │ └── strings.xml │ ├── values-tr/ │ │ └── strings.xml │ ├── values-uk/ │ │ └── strings.xml │ ├── values-v31/ │ │ └── arrays.xml │ ├── values-vi/ │ │ └── strings.xml │ ├── values-zh-rCN/ │ │ └── strings.xml │ ├── values-zh-rTW/ │ │ └── strings.xml │ └── xml/ │ ├── accessibility_service_config.xml │ ├── backup_rules.xml │ ├── circular_bar_preferences.xml │ ├── data_extraction_rules.xml │ ├── linear_bar_preferences.xml │ └── preferences.xml ├── build.gradle ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── renovate.json └── settings.gradle
Condensed preview — 136 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (392K chars).
[
{
"path": ".gitignore",
"chars": 109,
"preview": "*.iml\n.gradle\n/local.properties\n/.idea\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n"
},
{
"path": "README.md",
"chars": 1281,
"preview": "# Noti Progress Bar\n\n[<img alt=\"banner\" src=\"Images/Banner.png\" />](https://play.google.com/store/apps/details?id=com.gu"
},
{
"path": "app/.gitignore",
"chars": 51,
"preview": "/build\n/release/\n/src/debug/\n/google-services.json\n"
},
{
"path": "app/build.gradle",
"chars": 1980,
"preview": "plugins {\n id 'com.android.application'\n id 'org.jetbrains.kotlin.android'\n id 'com.google.gms.google-services'"
},
{
"path": "app/proguard-rules.pro",
"chars": 750,
"preview": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguar"
},
{
"path": "app/src/main/AndroidManifest.xml",
"chars": 2828,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:to"
},
{
"path": "app/src/main/java/com/gustavoas/noti/ProgressBarAppsAdapter.kt",
"chars": 5669,
"preview": "package com.gustavoas.noti\n\nimport android.content.Context\nimport android.content.Intent\nimport android.graphics.Color\ni"
},
{
"path": "app/src/main/java/com/gustavoas/noti/ProgressBarAppsRepository.kt",
"chars": 7945,
"preview": "package com.gustavoas.noti\n\nimport android.content.Context\nimport android.database.sqlite.SQLiteDatabase\nimport android."
},
{
"path": "app/src/main/java/com/gustavoas/noti/SettingsActivity.kt",
"chars": 16889,
"preview": "package com.gustavoas.noti\n\nimport android.content.Intent\nimport android.content.SharedPreferences\nimport android.net.Ur"
},
{
"path": "app/src/main/java/com/gustavoas/noti/Utils.kt",
"chars": 12502,
"preview": "package com.gustavoas.noti\n\nimport android.content.ComponentName\nimport android.content.Context\nimport android.content.p"
},
{
"path": "app/src/main/java/com/gustavoas/noti/fragments/BasePreferenceFragment.kt",
"chars": 557,
"preview": "package com.gustavoas.noti.fragments\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.preference.Prefe"
},
{
"path": "app/src/main/java/com/gustavoas/noti/fragments/CircularBarFragment.kt",
"chars": 1269,
"preview": "package com.gustavoas.noti.fragments\n\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport androidx."
},
{
"path": "app/src/main/java/com/gustavoas/noti/fragments/LinearBarFragment.kt",
"chars": 2199,
"preview": "package com.gustavoas.noti.fragments\n\nimport android.content.SharedPreferences\nimport android.os.Build\nimport android.os"
},
{
"path": "app/src/main/java/com/gustavoas/noti/fragments/PerAppSettingsFragment.kt",
"chars": 5009,
"preview": "package com.gustavoas.noti.fragments\n\nimport android.os.Build\nimport android.os.Build.VERSION_CODES\nimport android.os.Bu"
},
{
"path": "app/src/main/java/com/gustavoas/noti/fragments/SettingsFragment.kt",
"chars": 12375,
"preview": "package com.gustavoas.noti.fragments\n\nimport android.content.ComponentName\nimport android.content.Intent\nimport android."
},
{
"path": "app/src/main/java/com/gustavoas/noti/model/DeviceConfiguration.kt",
"chars": 324,
"preview": "package com.gustavoas.noti.model\n\ndata class DeviceConfiguration (\n var configuration: String? = null,\n var device"
},
{
"path": "app/src/main/java/com/gustavoas/noti/model/ProgressBarApp.kt",
"chars": 335,
"preview": "package com.gustavoas.noti.model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class"
},
{
"path": "app/src/main/java/com/gustavoas/noti/model/ProgressNotification.kt",
"chars": 243,
"preview": "package com.gustavoas.noti.model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class"
},
{
"path": "app/src/main/java/com/gustavoas/noti/notifications/DownloadProgressBar.kt",
"chars": 1427,
"preview": "package com.gustavoas.noti.notifications\n\nimport android.app.Notification\nimport android.content.Context\nimport android."
},
{
"path": "app/src/main/java/com/gustavoas/noti/notifications/GoogleTimerProgressBar.kt",
"chars": 1532,
"preview": "package com.gustavoas.noti.notifications\n\nimport android.content.Context\nimport android.service.notification.StatusBarNo"
},
{
"path": "app/src/main/java/com/gustavoas/noti/notifications/MediaProgressBar.kt",
"chars": 3439,
"preview": "package com.gustavoas.noti.notifications\n\nimport android.content.Context\nimport android.media.MediaMetadata\nimport andro"
},
{
"path": "app/src/main/java/com/gustavoas/noti/notifications/PercentageProgressBar.kt",
"chars": 2522,
"preview": "package com.gustavoas.noti.notifications\n\nimport android.app.Notification\nimport android.content.Context\nimport android."
},
{
"path": "app/src/main/java/com/gustavoas/noti/notifications/ProgressBarNotification.kt",
"chars": 4735,
"preview": "package com.gustavoas.noti.notifications\n\nimport android.app.Notification\nimport android.content.Context\nimport android."
},
{
"path": "app/src/main/java/com/gustavoas/noti/notifications/TimedProgressBar.kt",
"chars": 1982,
"preview": "package com.gustavoas.noti.notifications\n\nimport android.content.Context\nimport android.os.Handler\nimport android.os.Loo"
},
{
"path": "app/src/main/java/com/gustavoas/noti/preferences/BannerPreference.kt",
"chars": 1226,
"preview": "package com.gustavoas.noti.preferences\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.w"
},
{
"path": "app/src/main/java/com/gustavoas/noti/preferences/BarStylesListPreference.kt",
"chars": 4585,
"preview": "package com.gustavoas.noti.preferences\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.v"
},
{
"path": "app/src/main/java/com/gustavoas/noti/preferences/SeekBarPreference.kt",
"chars": 1820,
"preview": "package com.gustavoas.noti.preferences\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.v"
},
{
"path": "app/src/main/java/com/gustavoas/noti/services/AccessibilityService.kt",
"chars": 19342,
"preview": "package com.gustavoas.noti.services\n\nimport android.accessibilityservice.AccessibilityService\nimport android.animation.O"
},
{
"path": "app/src/main/java/com/gustavoas/noti/services/FullscreenDetectionService.kt",
"chars": 4832,
"preview": "package com.gustavoas.noti.services\n\nimport android.app.KeyguardManager\nimport android.app.Service\nimport android.conten"
},
{
"path": "app/src/main/java/com/gustavoas/noti/services/NotificationListenerService.kt",
"chars": 6059,
"preview": "package com.gustavoas.noti.services\n\nimport android.app.Notification\nimport android.os.Process\nimport android.service.no"
},
{
"path": "app/src/main/res/color/outlined_button_selector.xml",
"chars": 262,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "app/src/main/res/drawable/circular_mask.xml",
"chars": 338,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:co"
},
{
"path": "app/src/main/res/drawable/ic_accessibility.xml",
"chars": 651,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_apps.xml",
"chars": 1541,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_bug_report.xml",
"chars": 760,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"48dp\"\n android:height=\"48dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_calentile.xml",
"chars": 1294,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:height=\"28dp\"\n android:width=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_circular_progress_bar.xml",
"chars": 613,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_close.xml",
"chars": 413,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"24dp\"\n android:height=\"24dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_colors.xml",
"chars": 485,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_contrast.xml",
"chars": 676,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_download.xml",
"chars": 802,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_download_filled.xml",
"chars": 665,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_fullscreen.xml",
"chars": 536,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_height.xml",
"chars": 429,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"30dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_horizontal.xml",
"chars": 469,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_info.xml",
"chars": 895,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
"chars": 1778,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"108dp\"\n android:height=\"108dp\"\n"
},
{
"path": "app/src/main/res/drawable/ic_linear_progress_bar.xml",
"chars": 466,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_lockscreen.xml",
"chars": 665,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_margin_top.xml",
"chars": 781,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_music.xml",
"chars": 464,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_music_filled.xml",
"chars": 392,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_notification.xml",
"chars": 863,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"27dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_notification_bar.xml",
"chars": 390,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"27dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_overlay.xml",
"chars": 420,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_palette.xml",
"chars": 1464,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_preview.xml",
"chars": 975,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"48dp\"\n android:height=\"48dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_progress_bar_style.xml",
"chars": 623,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_rounded_corners.xml",
"chars": 858,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_share.xml",
"chars": 858,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_size.xml",
"chars": 632,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/ic_sumup.xml",
"chars": 1182,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"28dp\"\n android:height=\"28dp\"\n "
},
{
"path": "app/src/main/res/drawable/layout_bg.xml",
"chars": 213,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <corners a"
},
{
"path": "app/src/main/res/drawable/selector_download.xml",
"chars": 262,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "app/src/main/res/drawable/selector_music.xml",
"chars": 256,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <item a"
},
{
"path": "app/src/main/res/drawable/splashscreen.xml",
"chars": 1567,
"preview": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n android:width=\"30dp\"\n android:height=\"30dp\"\n "
},
{
"path": "app/src/main/res/drawable-v31/splashscreen.xml",
"chars": 3373,
"preview": "<animated-vector\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:aapt=\"http://schemas.android.c"
},
{
"path": "app/src/main/res/layout/activity_main.xml",
"chars": 2463,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout\n xmlns:android=\"http://sc"
},
{
"path": "app/src/main/res/layout/advanced_style_dialog.xml",
"chars": 2013,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView\n xmlns:android=\"http://schemas.android."
},
{
"path": "app/src/main/res/layout/app_item.xml",
"chars": 1578,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout/banner_preference.xml",
"chars": 2090,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView\n xmlns:android=\"http://sche"
},
{
"path": "app/src/main/res/layout/button_toggle_group.xml",
"chars": 3133,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://sche"
},
{
"path": "app/src/main/res/layout/fragment_per_app_settings.xml",
"chars": 1024,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout/material_switch.xml",
"chars": 357,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<!-- https://stackoverflow.com/a/76270479 -->\n<com.google.android.material.mater"
},
{
"path": "app/src/main/res/layout/per_app_settings_footer.xml",
"chars": 924,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmln"
},
{
"path": "app/src/main/res/layout/progress_bar.xml",
"chars": 2008,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas."
},
{
"path": "app/src/main/res/layout/seekbar_preference.xml",
"chars": 2193,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n "
},
{
"path": "app/src/main/res/layout-land/button_toggle_group.xml",
"chars": 3127,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout\n xmlns:android=\"http://sche"
},
{
"path": "app/src/main/res/menu/settings_menu.xml",
"chars": 376,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n xmlns:app=\"h"
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
"chars": 337,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
"chars": 337,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n <b"
},
{
"path": "app/src/main/res/resources.properties",
"chars": 23,
"preview": "unqualifiedResLocale=en"
},
{
"path": "app/src/main/res/values/arrays.xml",
"chars": 1900,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string-array name=\"progressBarStyle\">\n <item>@string/pref"
},
{
"path": "app/src/main/res/values/attrs.xml",
"chars": 298,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <declare-styleable name=\"HorizontalNumberPicker\">\n <attr n"
},
{
"path": "app/src/main/res/values/colors.xml",
"chars": 380,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"purple_500\">#FF6200EE</color>\n <color name=\"deep_"
},
{
"path": "app/src/main/res/values/dimens.xml",
"chars": 116,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <integer name=\"progress_bar_max\">10000</integer>\n</resources>"
},
{
"path": "app/src/main/res/values/ic_launcher_background.xml",
"chars": 135,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"ic_launcher_background\">@color/deep_purple_100</colo"
},
{
"path": "app/src/main/res/values/strings.xml",
"chars": 6497,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"app_name\" translatable=\"false\">Noti Progress Bar</s"
},
{
"path": "app/src/main/res/values/themes.xml",
"chars": 1805,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n <style name=\"Them"
},
{
"path": "app/src/main/res/values-ar/strings.xml",
"chars": 4989,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">تقرير الأخطاء</string>\n <string n"
},
{
"path": "app/src/main/res/values-ar-rSA/strings.xml",
"chars": 4481,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">تقرير الأخطاء</string>\n <string n"
},
{
"path": "app/src/main/res/values-bg/strings.xml",
"chars": 4733,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Докладвай за грешка</string>\n <st"
},
{
"path": "app/src/main/res/values-cs/strings.xml",
"chars": 6318,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Nahlásit chybu</string>\n <string "
},
{
"path": "app/src/main/res/values-de/strings.xml",
"chars": 6586,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"notificationListenerDescription\">Liest Benachrichti"
},
{
"path": "app/src/main/res/values-es/strings.xml",
"chars": 6543,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"perAppSettingsEmptyView\">Aún no se han encontrado b"
},
{
"path": "app/src/main/res/values-es-rMX/strings.xml",
"chars": 4779,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Informar error</string>\n <string "
},
{
"path": "app/src/main/res/values-fr/strings.xml",
"chars": 7044,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"colorWhite\">Blanc</string>\n <string name=\"reset\""
},
{
"path": "app/src/main/res/values-fr-rCA/strings.xml",
"chars": 62,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
},
{
"path": "app/src/main/res/values-hu/strings.xml",
"chars": 1902,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Hiba jelentése</string>\n <string "
},
{
"path": "app/src/main/res/values-in/strings.xml",
"chars": 3886,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Pelaporan Bug</string>\n <string n"
},
{
"path": "app/src/main/res/values-it/strings.xml",
"chars": 5169,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"prefsPerAppSettingsSummary\">Attiva noti in base all"
},
{
"path": "app/src/main/res/values-iw/strings.xml",
"chars": 4372,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">דווח על באג</string>\n <string nam"
},
{
"path": "app/src/main/res/values-ja/strings.xml",
"chars": 5015,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"notificationListenerDescription\">プログレスバー付きの通知を読み取りま"
},
{
"path": "app/src/main/res/values-ka/strings.xml",
"chars": 2314,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"reset\">საწყის პარამეტრებზე დაბრუნება</string>\n <"
},
{
"path": "app/src/main/res/values-lt/strings.xml",
"chars": 62,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources></resources>"
},
{
"path": "app/src/main/res/values-ml/strings.xml",
"chars": 6828,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">റിപ്പോർട്ട് ബഗ്ഗ്</string>\n <str"
},
{
"path": "app/src/main/res/values-nb-rNO/strings.xml",
"chars": 4642,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"colorOrange\">Oransje</string>\n <string name=\"col"
},
{
"path": "app/src/main/res/values-night/colors.xml",
"chars": 233,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <color name=\"text\">#FFFFFF</color>\n <color name=\"system_accent"
},
{
"path": "app/src/main/res/values-night/themes.xml",
"chars": 559,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n <style name=\"Theme"
},
{
"path": "app/src/main/res/values-pl/strings.xml",
"chars": 6224,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Zgłoś błąd</string>\n <string name"
},
{
"path": "app/src/main/res/values-pt-rPT/strings.xml",
"chars": 6683,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Reportar Erro</string>\n <string n"
},
{
"path": "app/src/main/res/values-ro/strings.xml",
"chars": 5074,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"reset\">Resetează</string>\n <string name=\"perAppS"
},
{
"path": "app/src/main/res/values-ru/strings.xml",
"chars": 181,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"settingsPreviewFab\">Предпросмотр</string>\n <stri"
},
{
"path": "app/src/main/res/values-sk/strings.xml",
"chars": 2679,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"settingsPreviewFab\">Náhľad</string>\n <string nam"
},
{
"path": "app/src/main/res/values-sl/strings.xml",
"chars": 993,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Prijavi napako</string>\n <string "
},
{
"path": "app/src/main/res/values-ta/strings.xml",
"chars": 6365,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"prefsNotificationPermissionSummary\">முன்னேற்றப் பட்"
},
{
"path": "app/src/main/res/values-tr/strings.xml",
"chars": 5278,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Hata Bildir</string>\n <string nam"
},
{
"path": "app/src/main/res/values-uk/strings.xml",
"chars": 6794,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"notificationListenerDescription\">Читає сповіщення з"
},
{
"path": "app/src/main/res/values-v31/arrays.xml",
"chars": 1522,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string-array name=\"colorsArray\">\n <item>@string/colorRed<"
},
{
"path": "app/src/main/res/values-vi/strings.xml",
"chars": 4637,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">Báo cáo lỗi</string>\n <string nam"
},
{
"path": "app/src/main/res/values-zh-rCN/strings.xml",
"chars": 4020,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"settingsPreviewFab\">预览</string>\n <string name=\"p"
},
{
"path": "app/src/main/res/values-zh-rTW/strings.xml",
"chars": 4669,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n <string name=\"menuReportBug\">回報錯誤</string>\n <string name=\"sett"
},
{
"path": "app/src/main/res/xml/accessibility_service_config.xml",
"chars": 196,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<accessibility-service\n xmlns:android=\"http://schemas.android.com/apk/res/andr"
},
{
"path": "app/src/main/res/xml/backup_rules.xml",
"chars": 116,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<full-backup-content>\n <include domain=\"database\"/>\n</full-backup-content>"
},
{
"path": "app/src/main/res/xml/circular_bar_preferences.xml",
"chars": 2681,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
},
{
"path": "app/src/main/res/xml/data_extraction_rules.xml",
"chars": 208,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<data-extraction-rules>\n <device-transfer>\n <include domain=\"database\"/"
},
{
"path": "app/src/main/res/xml/linear_bar_preferences.xml",
"chars": 1804,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
},
{
"path": "app/src/main/res/xml/preferences.xml",
"chars": 6150,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<PreferenceScreen\n xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
},
{
"path": "build.gradle",
"chars": 364,
"preview": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n id 'co"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 234,
"preview": "#Tue Jul 18 16:04:19 WEST 2023\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\:/"
},
{
"path": "gradle.properties",
"chars": 1358,
"preview": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will ov"
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2674,
"preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "renovate.json",
"chars": 114,
"preview": "{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n \"extends\": [\n \"config:recommended\"\n ]\n}\n"
},
{
"path": "settings.gradle",
"chars": 377,
"preview": "pluginManagement {\n repositories {\n google()\n mavenCentral()\n gradlePluginPortal()\n }\n}\ndepen"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the GustavoASantos/Noti GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 136 files (337.8 KB), approximately 93.0k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.