diff --git a/.gitignore b/.gitignore index ef02891..d9c34ee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea .gradle local.properties +build/ +.kotlin/ \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index ef1b9bd..34f3abe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -53,6 +53,7 @@ dependencies { implementation libs.aboutlibraries.core implementation libs.aboutlibraries implementation libs.preference.ktx + implementation libs.security.crypto testImplementation libs.junit androidTestImplementation libs.ext.junit diff --git a/app/src/main/java/com/klee/volumelockr/BootReceiver.kt b/app/src/main/java/com/klee/volumelockr/BootReceiver.kt index b83d9d9..832ce1a 100644 --- a/app/src/main/java/com/klee/volumelockr/BootReceiver.kt +++ b/app/src/main/java/com/klee/volumelockr/BootReceiver.kt @@ -9,7 +9,12 @@ class BootReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (intent.action == Intent.ACTION_BOOT_COMPLETED) { - VolumeService.start(context) + val prefs = context.getSharedPreferences(VolumeService.APP_SHARED_PREFERENCES, Context.MODE_PRIVATE) + val locks = prefs.getString(VolumeService.LOCKS_KEY, "") + + if (!locks.isNullOrEmpty() && locks != "{}") { + VolumeService.start(context) + } } } } diff --git a/app/src/main/java/com/klee/volumelockr/service/VolumeService.kt b/app/src/main/java/com/klee/volumelockr/service/VolumeService.kt index 2495aa8..8d6863f 100644 --- a/app/src/main/java/com/klee/volumelockr/service/VolumeService.kt +++ b/app/src/main/java/com/klee/volumelockr/service/VolumeService.kt @@ -1,6 +1,5 @@ package com.klee.volumelockr.service -import android.annotation.SuppressLint import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager @@ -91,6 +90,13 @@ class VolumeService : Service() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { tryShowNotification() } + + if (mVolumeLock.isEmpty()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + stopForeground(Service.STOP_FOREGROUND_REMOVE) + } + stopSelf() + } } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -262,10 +268,6 @@ class VolumeService : Service() { @RequiresApi(Build.VERSION_CODES.O) @Synchronized fun tryShowNotification() { - if (mVolumeLock.isEmpty()) { - return - } - createNotificationChannel() val notification = Notification.Builder(this, NOTIFICATION_CHANNEL_ID) .setContentTitle(NOTIFICATION_TITLE) @@ -277,7 +279,6 @@ class VolumeService : Service() { startForeground(NOTIFICATION_ID, notification) } - @SuppressLint("WrongConstant") @RequiresApi(Build.VERSION_CODES.N) @Synchronized fun tryHideNotification() { @@ -285,7 +286,8 @@ class VolumeService : Service() { return } - stopForeground(NOTIFICATION_ID) + stopForeground(Service.STOP_FOREGROUND_REMOVE) + stopSelf() } private fun createNotificationContentIntent(): PendingIntent { diff --git a/app/src/main/java/com/klee/volumelockr/ui/AboutFragment.kt b/app/src/main/java/com/klee/volumelockr/ui/AboutFragment.kt new file mode 100644 index 0000000..a507b22 --- /dev/null +++ b/app/src/main/java/com/klee/volumelockr/ui/AboutFragment.kt @@ -0,0 +1,57 @@ +package com.klee.volumelockr.ui + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.klee.volumelockr.R +import com.mikepenz.aboutlibraries.LibsBuilder + +class AboutFragment : Fragment() { + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment_about, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (savedInstanceState == null) { + val libsFragment = LibsBuilder() + .supportFragment() + + childFragmentManager.beginTransaction() + .add(R.id.about_libs_container, libsFragment) + .commit() + } + + view.post { + val recyclerView = findRecyclerView(view) + recyclerView?.let { rv -> + val spanCount = if (resources.getBoolean(R.bool.use_two_columns)) 2 else 1 + if (spanCount > 1) { + rv.layoutManager = GridLayoutManager(requireContext(), spanCount) + } + } + } + } + + private fun findRecyclerView(view: View): RecyclerView? { + if (view is RecyclerView) return view + if (view is ViewGroup) { + for (i in 0 until view.childCount) { + val child = view.getChildAt(i) + val rv = findRecyclerView(child) + if (rv != null) return rv + } + } + return null + } +} diff --git a/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt b/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt index 1c882c1..536c245 100644 --- a/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt +++ b/app/src/main/java/com/klee/volumelockr/ui/MainActivity.kt @@ -1,17 +1,17 @@ package com.klee.volumelockr.ui -import android.app.AlertDialog -import android.app.Dialog import android.app.NotificationManager -import android.content.Intent import android.os.Build import android.os.Bundle -import android.provider.Settings +import androidx.activity.enableEdgeToEdge import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat -import androidx.fragment.app.DialogFragment +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.setupWithNavController +import com.google.android.material.navigation.NavigationBarView import com.klee.volumelockr.R import com.klee.volumelockr.databinding.ActivityMainBinding @@ -20,10 +20,12 @@ class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { + enableEdgeToEdge() super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - handleEdgeToEdgeInsets() + setupNavigation() + setupWindowInsets() } override fun onResume() { @@ -34,6 +36,20 @@ class MainActivity : AppCompatActivity() { } } + private fun setupNavigation() { + setSupportActionBar(binding.toolbar) + val navHostFragment = supportFragmentManager + .findFragmentById(R.id.fragment_container_view) as NavHostFragment + val navController = navHostFragment.navController + val appBarConfiguration = AppBarConfiguration( + setOf(R.id.volumeSliderFragment, R.id.settingsFragment, R.id.about_libraries) + ) + binding.toolbar.setupWithNavController(navController, appBarConfiguration) + + val navView: NavigationBarView? = binding.bottomNavigation ?: binding.navigationRail + navView?.setupWithNavController(navController) + } + @RequiresApi(Build.VERSION_CODES.M) private fun checkDoNotDisturbPermission() { val notificationManager = @@ -44,32 +60,37 @@ class MainActivity : AppCompatActivity() { } } - class PolicyAccessDialog : DialogFragment() { - companion object { - const val TAG = "PolicyAccessDialog" - } - - @RequiresApi(Build.VERSION_CODES.M) - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = - AlertDialog.Builder(requireContext()) - .setMessage(getString(R.string.dialog_policy_access_title)) - .setCancelable(false) - .setPositiveButton(getString(R.string.dialog_allow)) { _, _ -> - startActivity(Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)) - } - .create() - } - - private fun handleEdgeToEdgeInsets() { - ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, windowInsets -> + private fun setupWindowInsets() { + ViewCompat.setOnApplyWindowInsetsListener(binding.appBarLayout) { v, windowInsets -> val bars = windowInsets.getInsets( WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() ) + v.setPadding(bars.left, bars.top, bars.right, 0) + WindowInsetsCompat.CONSUMED + } - v.setPadding(bars.left, bars.top, bars.right, bars.bottom) + binding.bottomNavigation?.let { view -> + ViewCompat.setOnApplyWindowInsetsListener(view) { v, windowInsets -> + val bars = windowInsets.getInsets( + WindowInsetsCompat.Type.systemBars() + or WindowInsetsCompat.Type.displayCutout() + ) + v.setPadding(bars.left, 0, bars.right, bars.bottom) + WindowInsetsCompat.CONSUMED + } + } - WindowInsetsCompat.CONSUMED + binding.navigationRail?.let { view -> + ViewCompat.setOnApplyWindowInsetsListener(view) { v, windowInsets -> + val bars = windowInsets.getInsets( + WindowInsetsCompat.Type.systemBars() + or WindowInsetsCompat.Type.displayCutout() + ) + // Rail needs top and bottom padding usually, and left padding + v.setPadding(bars.left, bars.top, 0, bars.bottom) + WindowInsetsCompat.CONSUMED + } } } } diff --git a/app/src/main/java/com/klee/volumelockr/ui/PolicyAccessDialog.kt b/app/src/main/java/com/klee/volumelockr/ui/PolicyAccessDialog.kt new file mode 100644 index 0000000..4664c0e --- /dev/null +++ b/app/src/main/java/com/klee/volumelockr/ui/PolicyAccessDialog.kt @@ -0,0 +1,27 @@ +package com.klee.volumelockr.ui + +import android.app.AlertDialog +import android.app.Dialog +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.provider.Settings +import androidx.annotation.RequiresApi +import androidx.fragment.app.DialogFragment +import com.klee.volumelockr.R + +class PolicyAccessDialog : DialogFragment() { + companion object { + const val TAG = "PolicyAccessDialog" + } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = + AlertDialog.Builder(requireContext()) + .setMessage(getString(R.string.dialog_policy_access_title)) + .setCancelable(false) + .setPositiveButton(getString(R.string.dialog_allow)) { _, _ -> + startActivity(Intent(Settings.ACTION_NOTIFICATION_POLICY_ACCESS_SETTINGS)) + } + .create() +} diff --git a/app/src/main/java/com/klee/volumelockr/ui/SettingsFragment.kt b/app/src/main/java/com/klee/volumelockr/ui/SettingsFragment.kt index 054cf71..d1deb59 100644 --- a/app/src/main/java/com/klee/volumelockr/ui/SettingsFragment.kt +++ b/app/src/main/java/com/klee/volumelockr/ui/SettingsFragment.kt @@ -1,36 +1,47 @@ package com.klee.volumelockr.ui -import android.app.AlertDialog import android.content.Context +import android.content.SharedPreferences import android.os.Bundle -import android.text.InputType +import android.util.Log +import android.view.LayoutInflater import android.view.View import android.view.inputmethod.InputMethodManager import android.widget.EditText -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat -import androidx.preference.EditTextPreference +import android.widget.Toast +import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import androidx.preference.PreferenceManager import androidx.preference.SwitchPreferenceCompat +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.textfield.TextInputLayout import com.klee.volumelockr.R import com.klee.volumelockr.service.VolumeService +import java.io.IOException +import java.security.GeneralSecurityException class SettingsFragment : PreferenceFragmentCompat() { companion object { + private const val TAG = "SettingsFragment" const val PASSWORD_PROTECTED_PREFERENCE = "password_protected" const val PASSWORD_CHANGE_PREFERENCE = "password" const val ALLOW_LOWER_PREFERENCE = "allow_lower" const val DELAY_IN_MS = 100L + const val MIN_PASSWORD_LENGTH = 6 + private const val ENCRYPTED_PREFS_FILE = "secure_settings" } + private var encryptedPrefs: SharedPreferences? = null + private lateinit var passwordProtected: SwitchPreferenceCompat - private lateinit var passwordChange: EditTextPreference + private lateinit var passwordChange: Preference private lateinit var shouldAllowLower: SwitchPreferenceCompat override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.root_preferences, rootKey) + initializeEncryptedPrefs() shouldAllowLower = findPreference(ALLOW_LOWER_PREFERENCE)!! passwordChange = findPreference(PASSWORD_CHANGE_PREFERENCE)!! @@ -42,12 +53,8 @@ class SettingsFragment : PreferenceFragmentCompat() { } passwordChange.isEnabled = !passwordProtected.isChecked - passwordChange.setOnBindEditTextListener { editText -> - editText.text.clear() - editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD - } - passwordChange.setOnPreferenceChangeListener { _, value -> - passwordProtected.isEnabled = value.toString().isNotEmpty() + passwordChange.setOnPreferenceClickListener { + showChangePasswordDialog() true } @@ -62,44 +69,114 @@ class SettingsFragment : PreferenceFragmentCompat() { passwordProtected.isEnabled = isPasswordSet() } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) + private fun initializeEncryptedPrefs() { + try { + val masterKey = MasterKey.Builder(requireContext()) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + + encryptedPrefs = EncryptedSharedPreferences.create( + requireContext(), + ENCRYPTED_PREFS_FILE, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM + ) + } catch (e: GeneralSecurityException) { + Log.e(TAG, "Failed to create encrypted preferences: security error", e) + Toast.makeText(context, R.string.password_save_error, Toast.LENGTH_LONG).show() + } catch (e: IOException) { + Log.e(TAG, "Failed to create encrypted preferences: IO error", e) + Toast.makeText(context, R.string.password_save_error, Toast.LENGTH_LONG).show() + } + } - val preferenceList = listView - val startPadding = preferenceList.paddingLeft - val topPadding = preferenceList.paddingTop - val endPadding = preferenceList.paddingRight - val bottomPadding = preferenceList.paddingBottom + private fun showChangePasswordDialog() { + val view = LayoutInflater.from(context).inflate(R.layout.dialog_password, null) + val inputLayout = view.findViewById(R.id.password_input_layout) + val editText = view.findViewById(android.R.id.edit) - ViewCompat.setOnApplyWindowInsetsListener(preferenceList) { v, windowInsets -> - val bars = windowInsets.getInsets( - WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() - ) + editText.setOnFocusChangeListener { _, _ -> + editText.postDelayed({ showKeyboard(editText) }, DELAY_IN_MS) + } + editText.requestFocus() - v.setPadding( - startPadding + bars.left, - topPadding + bars.top, - endPadding + bars.right, - bottomPadding + bars.bottom - ) + val dialog = MaterialAlertDialogBuilder(requireContext()) + .setTitle(getString(R.string.change_password)) + .setView(view) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, null) + .create() + + dialog.setOnShowListener { + dialog.getButton(android.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val password = editText.text.toString() + val validationError = validatePassword(password) + + if (validationError != null) { + inputLayout.error = validationError + } else { + inputLayout.error = null + if (savePassword(password)) { + dialog.dismiss() + } + } + } + } - WindowInsetsCompat.CONSUMED + dialog.show() + } + + private fun validatePassword(password: String): String? { + if (password.length < MIN_PASSWORD_LENGTH) { + return getString(R.string.password_too_short, MIN_PASSWORD_LENGTH) } + return null + } + + private fun savePassword(newPassword: String): Boolean { + val prefs = encryptedPrefs + if (prefs == null) { + Toast.makeText(context, R.string.password_save_error, Toast.LENGTH_SHORT).show() + return false + } + + return try { + prefs.edit() + .putString(PASSWORD_CHANGE_PREFERENCE, newPassword) + .apply() + passwordProtected.isEnabled = newPassword.isNotEmpty() + true + } catch (e: GeneralSecurityException) { + Log.e(TAG, "Failed to save password: security error", e) + Toast.makeText(context, R.string.password_save_error, Toast.LENGTH_SHORT).show() + false + } catch (e: IOException) { + Log.e(TAG, "Failed to save password: IO error", e) + Toast.makeText(context, R.string.password_save_error, Toast.LENGTH_SHORT).show() + false + } + } + + private fun getStoredPassword(): String { + return encryptedPrefs?.getString(PASSWORD_CHANGE_PREFERENCE, "") ?: "" } private fun askForPassword() { - val editText = EditText(context) - editText.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD + val view = LayoutInflater.from(context).inflate(R.layout.dialog_password, null) + val editText = view.findViewById(android.R.id.edit) + editText.setOnFocusChangeListener { _, _ -> editText.postDelayed({ showKeyboard(editText) }, DELAY_IN_MS) } editText.requestFocus() - AlertDialog.Builder(context) + MaterialAlertDialogBuilder(requireContext()) + .setIcon(R.drawable.ic_lock) .setTitle(getString(R.string.enter_password)) .setCancelable(false) - .setView(editText) - .setPositiveButton("OK") { _, _ -> + .setView(view) + .setPositiveButton(android.R.string.ok) { _, _ -> checkPassword(editText.text.toString()) } .show() @@ -111,21 +188,13 @@ class SettingsFragment : PreferenceFragmentCompat() { } private fun checkPassword(challenger: String) { - val password = - PreferenceManager.getDefaultSharedPreferences(requireContext()).getString( - PASSWORD_CHANGE_PREFERENCE, - "" - ) - - val isOk = password == challenger - passwordProtected.isChecked = !isOk - passwordChange.isEnabled = isOk + val storedPassword = getStoredPassword() + val isCorrect = storedPassword == challenger + passwordProtected.isChecked = !isCorrect + passwordChange.isEnabled = isCorrect } private fun isPasswordSet(): Boolean { - return PreferenceManager.getDefaultSharedPreferences(requireContext()).getString( - PASSWORD_CHANGE_PREFERENCE, - "" - )?.isNotEmpty()!! + return getStoredPassword().isNotEmpty() } } diff --git a/app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt b/app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt index 9b60e35..cb7f232 100644 --- a/app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt +++ b/app/src/main/java/com/klee/volumelockr/ui/VolumeSliderFragment.kt @@ -9,16 +9,9 @@ import android.os.Handler import android.os.IBinder import android.os.Looper import android.view.LayoutInflater -import android.view.Menu -import android.view.MenuInflater -import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.core.view.ViewCompat -import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearLayoutManager import com.klee.volumelockr.R import com.klee.volumelockr.databinding.FragmentVolumeSliderBinding import com.klee.volumelockr.service.VolumeService @@ -36,16 +29,10 @@ class VolumeSliderFragment : Fragment() { container: ViewGroup?, savedInstanceState: Bundle? ): View { - setHasOptionsMenu(true) _binding = FragmentVolumeSliderBinding.inflate(inflater, container, false) return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - handleEdgeToEdgeInsets() - } - override fun onResume() { super.onResume() mService?.let { @@ -67,49 +54,13 @@ class VolumeSliderFragment : Fragment() { super.onDestroyView() } - override fun onCreateOptionsMenu(menu: Menu, menuInflater: MenuInflater) { - val inflater: MenuInflater = menuInflater - inflater.inflate(R.menu.options, menu) - super.onCreateOptionsMenu(menu, menuInflater) - } - - override fun onOptionsItemSelected(item: MenuItem): Boolean { - when (item.itemId) { - R.id.about -> findNavController().navigate(R.id.action_x_to_about_libs) - R.id.options -> findNavController().navigate(R.id.action_sliders_to_settings) - } - return true - } - private fun setupRecyclerView(service: VolumeService) { - binding.recyclerView.layoutManager = LinearLayoutManager(requireContext()) + val spanCount = if (resources.getBoolean(R.bool.use_two_columns)) 2 else 1 + binding.recyclerView.layoutManager = androidx.recyclerview.widget.GridLayoutManager(requireContext(), spanCount) mAdapter = VolumeAdapter(service.getVolumes(), service, requireContext()) binding.recyclerView.adapter = mAdapter } - private fun handleEdgeToEdgeInsets() { - val recyclerView = binding.recyclerView - val startPadding = recyclerView.paddingLeft - val topPadding = recyclerView.paddingTop - val endPadding = recyclerView.paddingRight - val bottomPadding = recyclerView.paddingBottom - - ViewCompat.setOnApplyWindowInsetsListener(recyclerView) { v, insets -> - val bars = insets.getInsets( - WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout() - ) - - v.setPadding( - startPadding + bars.left, - topPadding + bars.top, - endPadding + bars.right, - bottomPadding + bars.bottom - ) - - WindowInsetsCompat.CONSUMED - } - } - private val connection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName?, service: IBinder?) { diff --git a/app/src/main/res/drawable/ic_home.xml b/app/src/main/res/drawable/ic_home.xml new file mode 100644 index 0000000..58cc391 --- /dev/null +++ b/app/src/main/res/drawable/ic_home.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml new file mode 100644 index 0000000..8cdd57f --- /dev/null +++ b/app/src/main/res/drawable/ic_info.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f5d0a14 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml new file mode 100644 index 0000000..c5d4ef6 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout-w600dp/activity_main.xml b/app/src/main/res/layout-w600dp/activity_main.xml new file mode 100644 index 0000000..6792117 --- /dev/null +++ b/app/src/main/res/layout-w600dp/activity_main.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index cd093ae..a620d88 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,6 +6,22 @@ android:layout_height="match_parent" tools:context=".ui.MainActivity"> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_password.xml b/app/src/main/res/layout/dialog_password.xml new file mode 100644 index 0000000..b0708fa --- /dev/null +++ b/app/src/main/res/layout/dialog_password.xml @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml new file mode 100644 index 0000000..50a4241 --- /dev/null +++ b/app/src/main/res/layout/fragment_about.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml new file mode 100644 index 0000000..2b9c9e5 --- /dev/null +++ b/app/src/main/res/menu/bottom_nav_menu.xml @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/app/src/main/res/navigation/navigation_graph.xml b/app/src/main/res/navigation/navigation_graph.xml index 2e557ff..4079252 100644 --- a/app/src/main/res/navigation/navigation_graph.xml +++ b/app/src/main/res/navigation/navigation_graph.xml @@ -5,12 +5,15 @@ android:id="@+id/navigation_graph" app:startDestination="@id/volumeSliderFragment"> - + + android:label="@string/app_name"> @@ -22,6 +25,6 @@ + android:label="@string/options" /> diff --git a/app/src/main/res/values-w600dp/bools.xml b/app/src/main/res/values-w600dp/bools.xml new file mode 100644 index 0000000..72df60f --- /dev/null +++ b/app/src/main/res/values-w600dp/bools.xml @@ -0,0 +1,4 @@ + + + true + diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml new file mode 100644 index 0000000..dcd6034 --- /dev/null +++ b/app/src/main/res/values/bools.xml @@ -0,0 +1,4 @@ + + + false + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..cabfc2e --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 24dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c581efb..97c7fff 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,25 +1,27 @@ VolumeLockr - Media - Alarm - Ringtones / Notifications - Voice calls - Title + VolumeLockr Lock - Settings + Options About - VolumeLockr - You must allow VolumeLockr to access Do Not Disturb mode + Home + To detect Ringer Mode changes (Silent, Vibrate, Normal), VolumeLockr needs \"Do Not Disturb\" access. Allow - true - true + Media + Alarm + Ring / Notification + Call - - Volume Limiting - Allow lower volume than limit Password Enable password protection Change password - Enter your password + Enter password + Password must be at least %d characters + Failed to save password + + Volume Limits + Allow lowering volume + + VolumeLockr \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index efd56ba..8ba7a27 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,11 +1,14 @@ -