diff --git a/app/build.gradle b/app/build.gradle
index f031253..6b6d9cb 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -25,9 +25,13 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
- implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
+ //noinspection GradleCompatible
+ // implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
+
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation project(':whatsappprofileimage')
+
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 692c635..85712ae 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,14 +1,20 @@
+
+
+
+ android:theme="@style/AppTheme"
+ tools:ignore="AllowBackup,ExtraText,GoogleAppIndexingWarning"
+ >
@@ -16,6 +22,7 @@
+
\ No newline at end of file
diff --git a/app/src/main/java/com/sample/waprofileimage/MainActivity.kt b/app/src/main/java/com/sample/waprofileimage/MainActivity.kt
index b3fcb7b..0ee3661 100644
--- a/app/src/main/java/com/sample/waprofileimage/MainActivity.kt
+++ b/app/src/main/java/com/sample/waprofileimage/MainActivity.kt
@@ -1,12 +1,42 @@
package com.sample.waprofileimage
-import android.support.v7.app.AppCompatActivity
+import android.content.Intent
import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.view.View
+import android.widget.Toast
+import com.mindorks.waprofileimage.waprofileImage.WAProfileImage
+import com.mindorks.waprofileimage.waprofileImage.WAProfileImage.IMAGE_PICKED_KEY
+import com.mindorks.waprofileimage.waprofileImage.wapprofileimageactivity.WAProfileImageActivity
+import android.graphics.BitmapFactory
+import android.graphics.Bitmap
+
-class MainActivity : AppCompatActivity() {
+class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
+ findViewById(R.id.sample_image).setOnClickListener { v ->
+ WAProfileImage.launch(this, 105)
+
+
+ }
+
+
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ if(resultCode==WAProfileImage.RESPONSE_CODE_OPEN_CAMERA)
+ {
+ if (data != null) {
+ // get image Path
+ val imagePath=data.getStringExtra(IMAGE_PICKED_KEY)
+
+ }
+ }
+
+
}
}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 84f1951..df77923 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -6,13 +6,17 @@
android:layout_height="match_parent"
tools:context=".MainActivity">
-
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_bias="0.11" />
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 22502aa..df65e12 100644
--- a/build.gradle
+++ b/build.gradle
@@ -8,7 +8,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:3.1.4'
+ classpath 'com.android.tools.build:gradle:3.2.0-beta05'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
diff --git a/commons/.gitignore b/commons/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/commons/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/commons/build.gradle b/commons/build.gradle
new file mode 100644
index 0000000..dffb6cd
--- /dev/null
+++ b/commons/build.gradle
@@ -0,0 +1,44 @@
+apply plugin: 'com.android.library'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
+
+android {
+ compileSdkVersion 28
+
+ defaultConfig {
+ minSdkVersion 19
+ targetSdkVersion 28
+ versionCode 1
+ versionName "1.0"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ consumerProguardFiles 'proguard-rules.pro'
+ }
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+}
+dependencies {
+ //noinspection GradleCompatible
+ // implementation 'com.android.support:appcompat-v7:27.1.1'
+ // implementation 'androidx.core:core-ktx:1.0.0-rc02'
+ implementation 'androidx.appcompat:appcompat:1.0.0-alpha3'
+
+ implementation 'com.github.bumptech.glide:glide:4.8.0'
+ implementation 'com.booking:rtlviewpager:1.0.1'
+ implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'com.bignerdranch.android:recyclerview-multiselect:0.2'
+ implementation 'com.google.code.gson:gson:2.8.5'
+ kapt 'com.github.bumptech.glide:compiler:4.8.0'
+
+}
+
diff --git a/commons/proguard-rules.pro b/commons/proguard-rules.pro
new file mode 100644
index 0000000..f965e77
--- /dev/null
+++ b/commons/proguard-rules.pro
@@ -0,0 +1,24 @@
+-renamesourcefileattribute SourceFile
+-keepattributes SourceFile, LineNumberTable
+
+-dontwarn com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool
+-dontwarn com.bumptech.glide.load.resource.bitmap.Downsampler
+-dontwarn com.bumptech.glide.load.resource.bitmap.HardwareConfigState
+-dontwarn com.bumptech.glide.manager.RequestManagerRetriever
+
+-keep public class * extends java.lang.Exception
+
+-keep class android.support.v7.widget.SearchView { *; }
+
+# Joda
+-dontwarn org.joda.convert.**
+-dontwarn org.joda.time.**
+-keep class org.joda.time.** { *; }
+-keep interface org.joda.time.** { *; }
+
+-keep public class * implements com.bumptech.glide.module.GlideModule
+-keep public class * extends com.bumptech.glide.module.AppGlideModule
+-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
+ **[] $VALUES;
+ public *;
+}
diff --git a/commons/src/main/AndroidManifest.xml b/commons/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..4e5a1d6
--- /dev/null
+++ b/commons/src/main/AndroidManifest.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/activities/AboutActivity.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/AboutActivity.kt
new file mode 100644
index 0000000..28dc1c7
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/AboutActivity.kt
@@ -0,0 +1,179 @@
+package com.simplemobiletools.commons.activities
+
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.text.Html
+import android.text.method.LinkMovementMethod
+import android.view.View
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.dialogs.ConfirmationDialog
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.*
+import com.simplemobiletools.commons.models.FAQItem
+import kotlinx.android.synthetic.main.activity_about.*
+import java.util.*
+
+class AboutActivity : BaseSimpleActivity() {
+ private var appName = ""
+ private var linkColor = 0
+
+ override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList()
+
+ override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: ""
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_about)
+ appName = intent.getStringExtra(APP_NAME) ?: ""
+ linkColor = getAdjustedPrimaryColor()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ updateTextColors(about_holder)
+
+ setupWebsite()
+ setupEmail()
+ setupFAQ()
+ setupMoreApps()
+ setupRateUs()
+ setupInvite()
+ setupLicense()
+ setupFacebook()
+ setupGPlus()
+ setupCopyright()
+ }
+
+ private fun setupWebsite() {
+ val websiteText = String.format(getString(R.string.two_string_placeholder), getString(R.string.website_label), getString(R.string.my_website))
+ about_website.text = websiteText
+ }
+
+ private fun setupEmail() {
+ val label = getString(R.string.email_label)
+ val email = getString(R.string.my_email)
+
+ val appVersion = String.format(getString(R.string.app_version, intent.getStringExtra(APP_VERSION_NAME)))
+ val deviceOS = String.format(getString(R.string.device_os), Build.VERSION.RELEASE)
+ val newline = "%0D%0A"
+ val separator = "------------------------------"
+ val body = "$appVersion$newline$deviceOS$newline$separator$newline$newline$newline"
+ val href = "$label$email "
+ about_email.text = Html.fromHtml(href)
+
+ if (intent.getBooleanExtra(SHOW_FAQ_BEFORE_MAIL, false) && !baseConfig.wasBeforeAskingShown) {
+ about_email.setOnClickListener {
+ baseConfig.wasBeforeAskingShown = true
+ about_email.movementMethod = LinkMovementMethod.getInstance()
+ about_email.setOnClickListener(null)
+ ConfirmationDialog(this, "", R.string.before_asking_question_read_faq, R.string.read_it, R.string.skip) {
+ about_faq_label.performClick()
+ }
+ }
+ } else {
+ about_email.movementMethod = LinkMovementMethod.getInstance()
+ }
+ }
+
+ private fun setupFAQ() {
+ val faqItems = intent.getSerializableExtra(APP_FAQ) as ArrayList
+ about_faq_label.beVisibleIf(faqItems.isNotEmpty())
+ about_faq_label.setOnClickListener {
+ openFAQ(faqItems)
+ }
+
+ about_faq.beVisibleIf(faqItems.isNotEmpty())
+ about_faq.setOnClickListener {
+ openFAQ(faqItems)
+ }
+
+ about_faq.setTextColor(linkColor)
+ about_faq.underlineText()
+ }
+
+ private fun openFAQ(faqItems: ArrayList) {
+ Intent(applicationContext, FAQActivity::class.java).apply {
+ putExtra(APP_ICON_IDS, getAppIconIDs())
+ putExtra(APP_LAUNCHER_NAME, getAppLauncherName())
+ putExtra(APP_FAQ, faqItems)
+ startActivity(this)
+ }
+ }
+
+ private fun setupMoreApps() {
+ about_more_apps.setOnClickListener {
+ launchViewIntent("https://play.google.com/store/apps/dev?id=9070296388022589266")
+ }
+ about_more_apps.setTextColor(linkColor)
+ }
+
+ private fun setupInvite() {
+ about_invite.setOnClickListener {
+ val text = String.format(getString(R.string.share_text), appName, getStoreUrl())
+ Intent().apply {
+ action = Intent.ACTION_SEND
+ putExtra(Intent.EXTRA_SUBJECT, appName)
+ putExtra(Intent.EXTRA_TEXT, text)
+ type = "text/plain"
+ startActivity(Intent.createChooser(this, getString(R.string.invite_via)))
+ }
+ }
+ about_invite.setTextColor(linkColor)
+ }
+
+ private fun setupRateUs() {
+ if (baseConfig.appRunCount < 10) {
+ about_rate_us.visibility = View.GONE
+ } else {
+ about_rate_us.setOnClickListener {
+ try {
+ launchViewIntent("market://details?id=$packageName")
+ } catch (ignored: ActivityNotFoundException) {
+ launchViewIntent(getStoreUrl())
+ }
+ }
+ }
+ about_rate_us.setTextColor(linkColor)
+ }
+
+ private fun setupLicense() {
+ about_license.setOnClickListener {
+ Intent(applicationContext, LicenseActivity::class.java).apply {
+ putExtra(APP_ICON_IDS, getAppIconIDs())
+ putExtra(APP_LAUNCHER_NAME, getAppLauncherName())
+ putExtra(APP_LICENSES, intent.getIntExtra(APP_LICENSES, 0))
+ startActivity(this)
+ }
+ }
+ about_license.setTextColor(linkColor)
+ }
+
+ private fun setupFacebook() {
+ about_facebook.setOnClickListener {
+ var link = "https://www.facebook.com/simplemobiletools"
+ try {
+ packageManager.getPackageInfo("com.facebook.katana", 0)
+ link = "fb://page/150270895341774"
+ } catch (ignored: Exception) {
+ }
+
+ launchViewIntent(link)
+ }
+ }
+
+ private fun setupGPlus() {
+ about_gplus.setOnClickListener {
+ launchViewIntent("https://plus.google.com/communities/104880861558693868382")
+ }
+ }
+
+ private fun setupCopyright() {
+ val versionName = intent.getStringExtra(APP_VERSION_NAME) ?: ""
+ val year = Calendar.getInstance().get(Calendar.YEAR)
+ about_copyright.text = String.format(getString(R.string.copyright), versionName, year)
+ }
+
+ private fun getStoreUrl() = "https://play.google.com/store/apps/details?id=$packageName"
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/activities/BaseSimpleActivity.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/BaseSimpleActivity.kt
new file mode 100644
index 0000000..6073e64
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/BaseSimpleActivity.kt
@@ -0,0 +1,396 @@
+package com.simplemobiletools.commons.activities
+
+import android.annotation.SuppressLint
+import android.annotation.TargetApi
+import android.app.Activity
+import android.app.ActivityManager
+import android.content.Context
+import android.content.Intent
+import android.graphics.BitmapFactory
+import android.graphics.drawable.ColorDrawable
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.provider.DocumentsContract
+import android.support.v4.app.ActivityCompat
+import android.support.v4.util.Pair
+import android.support.v7.app.AppCompatActivity
+import android.view.MenuItem
+import android.view.WindowManager
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.asynctasks.CopyMoveTask
+import com.simplemobiletools.commons.dialogs.ConfirmationDialog
+import com.simplemobiletools.commons.dialogs.FileConflictDialog
+import com.simplemobiletools.commons.dialogs.WritePermissionDialog
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.*
+import com.simplemobiletools.commons.interfaces.CopyMoveListener
+import com.simplemobiletools.commons.models.FAQItem
+import com.simplemobiletools.commons.models.FileDirItem
+import java.io.File
+import java.util.*
+
+abstract class BaseSimpleActivity : AppCompatActivity() {
+ var copyMoveCallback: ((destinationPath: String) -> Unit)? = null
+ var actionOnPermission: ((granted: Boolean) -> Unit)? = null
+ var isAskingPermissions = false
+ var useDynamicTheme = true
+
+ private val GENERIC_PERM_HANDLER = 100
+
+ companion object {
+ var funAfterSAFPermission: (() -> Unit)? = null
+ var funAfterOTGPermission: ((success: Boolean) -> Unit)? = null
+ }
+
+ abstract fun getAppIconIDs(): ArrayList
+
+ abstract fun getAppLauncherName(): String
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ if (useDynamicTheme) {
+ setTheme(getThemeId())
+ }
+
+ super.onCreate(savedInstanceState)
+ if (!packageName.startsWith("com.simplemobiletools.", true)) {
+ if ((0..50).random() == 10 || baseConfig.appRunCount % 100 == 0) {
+ val label = "You are using a fake version of the app. For your own safety download the original one from www.simplemobiletools.com. Thanks"
+ ConfirmationDialog(this, label, positive = R.string.ok, negative = 0) {
+ launchViewIntent("https://play.google.com/store/apps/dev?id=9070296388022589266")
+ }
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (useDynamicTheme) {
+ setTheme(getThemeId())
+ updateBackgroundColor()
+ }
+ updateActionbarColor()
+ updateRecentsAppIcon()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ actionOnPermission = null
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ funAfterSAFPermission = null
+ funAfterOTGPermission = null
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
+ android.R.id.home -> {
+ finish()
+ true
+ }
+ else -> super.onOptionsItemSelected(item)
+ }
+
+ override fun attachBaseContext(newBase: Context) {
+ if (newBase.baseConfig.useEnglish) {
+ super.attachBaseContext(MyContextWrapper(newBase).wrap(newBase, "en"))
+ } else {
+ super.attachBaseContext(newBase)
+ }
+ }
+
+ fun updateBackgroundColor(color: Int = baseConfig.backgroundColor) {
+ window.decorView.setBackgroundColor(color)
+ }
+
+ fun updateActionbarColor(color: Int = baseConfig.primaryColor) {
+ supportActionBar?.setBackgroundDrawable(ColorDrawable(color))
+ updateActionBarTitle(supportActionBar?.title.toString(), color)
+ updateStatusbarColor(color)
+
+ if (isLollipopPlus()) {
+ setTaskDescription(ActivityManager.TaskDescription(null, null, color))
+ }
+ }
+
+ fun updateStatusbarColor(color: Int) {
+ if (isLollipopPlus()) {
+ window.statusBarColor = color.darkenColor()
+ }
+ }
+
+ fun updateRecentsAppIcon() {
+ if (baseConfig.isUsingModifiedAppIcon && isLollipopPlus()) {
+ val appIconIDs = getAppIconIDs()
+ val currentAppIconColorIndex = getCurrentAppIconColorIndex()
+ if (appIconIDs.size - 1 < currentAppIconColorIndex) {
+ return
+ }
+
+ val recentsIcon = BitmapFactory.decodeResource(resources, appIconIDs[currentAppIconColorIndex])
+ val title = getAppLauncherName()
+ val color = baseConfig.primaryColor
+
+ val description = ActivityManager.TaskDescription(title, recentsIcon, color)
+ setTaskDescription(description)
+ }
+ }
+
+ private fun getCurrentAppIconColorIndex(): Int {
+ val appIconColor = baseConfig.appIconColor
+ getAppIconColors().forEachIndexed { index, color ->
+ if (color == appIconColor) {
+ return index
+ }
+ }
+ return 0
+ }
+
+ fun setTranslucentNavigation() {
+ if (isKitkatPlus()) {
+ window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION)
+ }
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
+ super.onActivityResult(requestCode, resultCode, resultData)
+ if (requestCode == OPEN_DOCUMENT_TREE && resultCode == Activity.RESULT_OK && resultData != null) {
+ if (isProperSDFolder(resultData.data)) {
+ if (resultData.dataString == baseConfig.OTGTreeUri) {
+ toast(R.string.sd_card_otg_same)
+ return
+ }
+ saveTreeUri(resultData)
+ funAfterSAFPermission?.invoke()
+ funAfterSAFPermission = null
+ } else {
+ toast(R.string.wrong_root_selected)
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ startActivityForResult(intent, requestCode)
+ }
+ } else if (requestCode == OPEN_DOCUMENT_TREE_OTG && resultCode == Activity.RESULT_OK && resultData != null) {
+ if (isProperOTGFolder(resultData.data)) {
+ if (resultData.dataString == baseConfig.treeUri) {
+ funAfterOTGPermission?.invoke(false)
+ toast(R.string.sd_card_otg_same)
+ return
+ }
+ baseConfig.OTGTreeUri = resultData.dataString
+ baseConfig.OTGPartition = baseConfig.OTGTreeUri.removeSuffix("%3A").substringAfterLast('/')
+
+ funAfterOTGPermission?.invoke(true)
+ funAfterOTGPermission = null
+ } else {
+ toast(R.string.wrong_root_selected_otg)
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
+ startActivityForResult(intent, requestCode)
+ }
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private fun saveTreeUri(resultData: Intent) {
+ val treeUri = resultData.data
+ baseConfig.treeUri = treeUri.toString()
+
+ val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ applicationContext.contentResolver.takePersistableUriPermission(treeUri, takeFlags)
+ }
+
+ private fun isProperSDFolder(uri: Uri) = isExternalStorageDocument(uri) && isRootUri(uri) && !isInternalStorage(uri)
+
+ private fun isProperOTGFolder(uri: Uri) = isExternalStorageDocument(uri) && isRootUri(uri) && !isInternalStorage(uri)
+
+ @SuppressLint("NewApi")
+ private fun isRootUri(uri: Uri) = DocumentsContract.getTreeDocumentId(uri).endsWith(":")
+
+ @SuppressLint("NewApi")
+ private fun isInternalStorage(uri: Uri) = isExternalStorageDocument(uri) && DocumentsContract.getTreeDocumentId(uri).contains("primary")
+
+ private fun isExternalStorageDocument(uri: Uri) = "com.android.externalstorage.documents" == uri.authority
+
+ fun startAboutActivity(appNameId: Int, licenseMask: Int, versionName: String, faqItems: ArrayList, showFAQBeforeMail: Boolean) {
+ Intent(applicationContext, AboutActivity::class.java).apply {
+ putExtra(APP_ICON_IDS, getAppIconIDs())
+ putExtra(APP_LAUNCHER_NAME, getAppLauncherName())
+ putExtra(APP_NAME, getString(appNameId))
+ putExtra(APP_LICENSES, licenseMask)
+ putExtra(APP_VERSION_NAME, versionName)
+ putExtra(APP_FAQ, faqItems)
+ putExtra(SHOW_FAQ_BEFORE_MAIL, showFAQBeforeMail)
+ startActivity(this)
+ }
+ }
+
+ fun startCustomizationActivity() {
+ Intent(applicationContext, CustomizationActivity::class.java).apply {
+ putExtra(APP_ICON_IDS, getAppIconIDs())
+ putExtra(APP_LAUNCHER_NAME, getAppLauncherName())
+ startActivity(this)
+ }
+ }
+
+ fun handleSAFDialog(path: String, callback: () -> Unit): Boolean {
+ return if (!path.startsWith(OTG_PATH) && isShowingSAFDialog(path, baseConfig.treeUri, OPEN_DOCUMENT_TREE)) {
+ funAfterSAFPermission = callback
+ true
+ } else {
+ callback()
+ false
+ }
+ }
+
+ fun copyMoveFilesTo(fileDirItems: ArrayList, source: String, destination: String, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean,
+ copyHidden: Boolean, callback: (destinationPath: String) -> Unit) {
+ if (source == destination) {
+ toast(R.string.source_and_destination_same)
+ return
+ }
+
+ if (!getDoesFilePathExist(destination)) {
+ toast(R.string.invalid_destination)
+ return
+ }
+
+ handleSAFDialog(destination) {
+ copyMoveCallback = callback
+ if (isCopyOperation) {
+ startCopyMove(fileDirItems, destination, isCopyOperation, copyPhotoVideoOnly, copyHidden)
+ } else {
+ if (source.startsWith(OTG_PATH) || destination.startsWith(OTG_PATH) || isPathOnSD(source) || isPathOnSD(destination) || fileDirItems.first().isDirectory) {
+ handleSAFDialog(source) {
+ startCopyMove(fileDirItems, destination, isCopyOperation, copyPhotoVideoOnly, copyHidden)
+ }
+ } else {
+ toast(R.string.moving)
+ val updatedFiles = ArrayList(fileDirItems.size * 2)
+ try {
+ val destinationFolder = File(destination)
+ for (oldFileDirItem in fileDirItems) {
+ val newFile = File(destinationFolder, oldFileDirItem.name)
+ if (!newFile.exists() && File(oldFileDirItem.path).renameTo(newFile)) {
+ if (!baseConfig.keepLastModified) {
+ newFile.setLastModified(System.currentTimeMillis())
+ }
+ updatedFiles.add(newFile.toFileDirItem(applicationContext))
+ }
+ }
+
+ val updatedPaths = updatedFiles.map { it.path } as ArrayList
+ rescanPaths(updatedPaths) {
+ runOnUiThread {
+ copyMoveListener.copySucceeded(false, fileDirItems.size == updatedFiles.size, destination)
+ }
+ }
+
+ if (updatedPaths.isEmpty()) {
+ copyMoveListener.copySucceeded(false, false, destination)
+ }
+ } catch (e: Exception) {
+ showErrorToast(e)
+ }
+ }
+ }
+ }
+ }
+
+ private fun startCopyMove(files: ArrayList, destinationPath: String, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, copyHidden: Boolean) {
+ checkConflicts(files, destinationPath, 0, LinkedHashMap()) {
+ toast(if (isCopyOperation) R.string.copying else R.string.moving)
+ val pair = Pair(files, destinationPath)
+ CopyMoveTask(this, isCopyOperation, copyPhotoVideoOnly, it, copyMoveListener, copyHidden).execute(pair)
+ }
+ }
+
+ fun checkConflicts(files: ArrayList, destinationPath: String, index: Int, conflictResolutions: LinkedHashMap,
+ callback: (resolutions: LinkedHashMap) -> Unit) {
+ if (index == files.size) {
+ callback(conflictResolutions)
+ return
+ }
+
+ val file = files[index]
+ val newFileDirItem = FileDirItem("$destinationPath/${file.name}", file.name, file.isDirectory)
+ if (getDoesFilePathExist(newFileDirItem.path)) {
+ FileConflictDialog(this, newFileDirItem) { resolution, applyForAll ->
+ if (applyForAll) {
+ conflictResolutions.clear()
+ conflictResolutions[""] = resolution
+ checkConflicts(files, destinationPath, files.size, conflictResolutions, callback)
+ } else {
+ conflictResolutions[newFileDirItem.path] = resolution
+ checkConflicts(files, destinationPath, index + 1, conflictResolutions, callback)
+ }
+ }
+ } else {
+ checkConflicts(files, destinationPath, index + 1, conflictResolutions, callback)
+ }
+ }
+
+ fun handlePermission(permissionId: Int, callback: (granted: Boolean) -> Unit) {
+ actionOnPermission = null
+ if (hasPermission(permissionId)) {
+ callback(true)
+ } else {
+ isAskingPermissions = true
+ actionOnPermission = callback
+ ActivityCompat.requestPermissions(this, arrayOf(getPermissionString(permissionId)), GENERIC_PERM_HANDLER)
+ }
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ isAskingPermissions = false
+ if (requestCode == GENERIC_PERM_HANDLER && grantResults.isNotEmpty()) {
+ actionOnPermission?.invoke(grantResults[0] == 0)
+ }
+ }
+
+ val copyMoveListener = object : CopyMoveListener {
+ override fun copySucceeded(copyOnly: Boolean, copiedAll: Boolean, destinationPath: String) {
+ if (copyOnly) {
+ toast(if (copiedAll) R.string.copying_success else R.string.copying_success_partial)
+ } else {
+ toast(if (copiedAll) R.string.moving_success else R.string.moving_success_partial)
+ }
+
+ copyMoveCallback?.invoke(destinationPath)
+ copyMoveCallback = null
+ }
+
+ override fun copyFailed() {
+ toast(R.string.copy_move_failed)
+ copyMoveCallback = null
+ }
+ }
+
+ fun handleOTGPermission(callback: (success: Boolean) -> Unit) {
+ if (baseConfig.OTGTreeUri.isNotEmpty()) {
+ callback(true)
+ return
+ }
+
+ funAfterOTGPermission = callback
+ WritePermissionDialog(this, true) {
+ Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
+ if (resolveActivity(packageManager) == null) {
+ type = "*/*"
+ }
+
+ if (resolveActivity(packageManager) != null) {
+ startActivityForResult(this, OPEN_DOCUMENT_TREE_OTG)
+ } else {
+ toast(R.string.unknown_error_occurred)
+ }
+ }
+ }
+ }
+
+ fun checkAppOnSDCard() {
+ if (!baseConfig.wasAppOnSDShown && isAppInstalledOnSDCard()) {
+ baseConfig.wasAppOnSDShown = true
+ ConfirmationDialog(this, "", R.string.app_on_sd_card, R.string.ok, 0) {}
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/activities/BaseSplashActivity.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/BaseSplashActivity.kt
new file mode 100644
index 0000000..4eb3001
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/BaseSplashActivity.kt
@@ -0,0 +1,36 @@
+package com.simplemobiletools.commons.activities
+
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import com.simplemobiletools.commons.extensions.baseConfig
+import com.simplemobiletools.commons.extensions.getSharedTheme
+import com.simplemobiletools.commons.extensions.isThankYouInstalled
+
+abstract class BaseSplashActivity : AppCompatActivity() {
+
+ abstract fun initActivity()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if ((baseConfig.appRunCount == 0 || !baseConfig.wasSharedThemeAfterUpdateChecked) && isThankYouInstalled()) {
+ baseConfig.wasSharedThemeAfterUpdateChecked = true
+ getSharedTheme {
+ if (it != null) {
+ baseConfig.apply {
+ wasSharedThemeForced = true
+ isUsingSharedTheme = true
+ wasSharedThemeEverActivated = true
+
+ textColor = it.textColor
+ backgroundColor = it.backgroundColor
+ primaryColor = it.primaryColor
+ appIconColor = it.appIconColor
+ }
+ }
+ initActivity()
+ }
+ } else {
+ initActivity()
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/activities/CustomizationActivity.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/CustomizationActivity.kt
new file mode 100644
index 0000000..4daf71b
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/CustomizationActivity.kt
@@ -0,0 +1,403 @@
+package com.simplemobiletools.commons.activities
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.dialogs.*
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.APP_ICON_IDS
+import com.simplemobiletools.commons.helpers.APP_LAUNCHER_NAME
+import com.simplemobiletools.commons.helpers.MyContentProvider
+import com.simplemobiletools.commons.models.MyTheme
+import com.simplemobiletools.commons.models.RadioItem
+import com.simplemobiletools.commons.models.SharedTheme
+import kotlinx.android.synthetic.main.activity_customization.*
+import java.util.*
+
+class CustomizationActivity : BaseSimpleActivity() {
+ private val THEME_LIGHT = 0
+ private val THEME_DARK = 1
+ private val THEME_SOLARIZED = 2
+ private val THEME_DARK_RED = 3
+ private val THEME_BLACK_WHITE = 4
+ private val THEME_CUSTOM = 5
+ private val THEME_SHARED = 6
+
+ private var curTextColor = 0
+ private var curBackgroundColor = 0
+ private var curPrimaryColor = 0
+ private var curAppIconColor = 0
+ private var curSelectedThemeId = 0
+ private var originalAppIconColor = 0
+ private var hasUnsavedChanges = false
+ private var predefinedThemes = LinkedHashMap()
+ private var curPrimaryLineColorPicker: LineColorPickerDialog? = null
+ private var storedSharedTheme: SharedTheme? = null
+
+ override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList()
+
+ override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: ""
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_customization)
+
+ initColorVariables()
+ setupColorsPickers()
+
+ if (isThankYouInstalled()) {
+ val cursorLoader = getMyContentProviderCursorLoader()
+ Thread {
+ try {
+ storedSharedTheme = getSharedThemeSync(cursorLoader)
+ if (storedSharedTheme == null) {
+ baseConfig.isUsingSharedTheme = false
+ } else {
+ baseConfig.wasSharedThemeEverActivated = true
+ }
+
+ runOnUiThread {
+ setupThemes()
+ apply_to_all_holder.beVisibleIf(storedSharedTheme == null)
+ }
+ } catch (e: Exception) {
+ toast(R.string.update_thank_you)
+ finish()
+ }
+ }.start()
+ } else {
+ setupThemes()
+ baseConfig.isUsingSharedTheme = false
+ }
+
+ supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_cross)
+ updateTextColors(customization_holder)
+ originalAppIconColor = baseConfig.appIconColor
+ }
+
+ override fun onResume() {
+ super.onResume()
+ updateBackgroundColor(curBackgroundColor)
+ updateActionbarColor(curPrimaryColor)
+ setTheme(getThemeId(curPrimaryColor))
+
+ curPrimaryLineColorPicker?.getSpecificColor()?.apply {
+ updateActionbarColor(this)
+ setTheme(getThemeId(this))
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu_customization, menu)
+ menu.findItem(R.id.save).isVisible = hasUnsavedChanges
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.save -> saveChanges(true)
+ else -> return super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ override fun onBackPressed() {
+ if (hasUnsavedChanges) {
+ promptSaveDiscard()
+ } else {
+ super.onBackPressed()
+ }
+ }
+
+ private fun setupThemes() {
+ predefinedThemes.apply {
+ put(THEME_LIGHT, MyTheme(R.string.light_theme, R.color.theme_light_text_color, R.color.theme_light_background_color, R.color.color_primary, R.color.color_primary))
+ put(THEME_DARK, MyTheme(R.string.dark_theme, R.color.theme_dark_text_color, R.color.theme_dark_background_color, R.color.color_primary, R.color.color_primary))
+ //put(THEME_SOLARIZED, MyTheme(R.string.solarized, R.color.theme_solarized_text_color, R.color.theme_solarized_background_color, R.color.theme_solarized_primary_color))
+ put(THEME_DARK_RED, MyTheme(R.string.dark_red, R.color.theme_dark_text_color, R.color.theme_dark_background_color, R.color.theme_dark_red_primary_color, R.color.md_red_700))
+ put(THEME_BLACK_WHITE, MyTheme(R.string.black_white, android.R.color.white, android.R.color.black, android.R.color.black, R.color.md_grey_black))
+ put(THEME_CUSTOM, MyTheme(R.string.custom, 0, 0, 0, 0))
+
+ if (storedSharedTheme != null) {
+ put(THEME_SHARED, MyTheme(R.string.shared, 0, 0, 0, 0))
+ }
+ }
+ setupThemePicker()
+ }
+
+ private fun setupThemePicker() {
+ curSelectedThemeId = getCurrentThemeId()
+ customization_theme.text = getThemeText()
+ customization_theme_holder.setOnClickListener {
+ val items = arrayListOf()
+ for ((key, value) in predefinedThemes) {
+ items.add(RadioItem(key, getString(value.nameId)))
+ }
+
+ RadioGroupDialog(this@CustomizationActivity, items, curSelectedThemeId) {
+ if (it == THEME_SHARED && !isThankYouInstalled()) {
+ PurchaseThankYouDialog(this)
+ return@RadioGroupDialog
+ }
+
+ updateColorTheme(it as Int, true)
+ if (it != THEME_CUSTOM && it != THEME_SHARED && !baseConfig.wasCustomThemeSwitchDescriptionShown) {
+ baseConfig.wasCustomThemeSwitchDescriptionShown = true
+ toast(R.string.changing_color_description)
+ }
+ }
+ }
+ }
+
+ private fun updateColorTheme(themeId: Int, useStored: Boolean = false) {
+ curSelectedThemeId = themeId
+ customization_theme.text = getThemeText()
+
+ resources.apply {
+ if (curSelectedThemeId == THEME_CUSTOM) {
+ if (useStored) {
+ curTextColor = baseConfig.customTextColor
+ curBackgroundColor = baseConfig.customBackgroundColor
+ curPrimaryColor = baseConfig.customPrimaryColor
+ curAppIconColor = baseConfig.customAppIconColor
+ setTheme(getThemeId(curPrimaryColor))
+ setupColorsPickers()
+ } else {
+ baseConfig.customPrimaryColor = curPrimaryColor
+ baseConfig.customBackgroundColor = curBackgroundColor
+ baseConfig.customTextColor = curTextColor
+ baseConfig.appIconColor = curAppIconColor
+ }
+ } else if (curSelectedThemeId == THEME_SHARED) {
+ if (useStored) {
+ storedSharedTheme?.apply {
+ curTextColor = textColor
+ curBackgroundColor = backgroundColor
+ curPrimaryColor = primaryColor
+ curAppIconColor = appIconColor
+ }
+ setTheme(getThemeId(curPrimaryColor))
+ setupColorsPickers()
+ }
+ } else {
+ val theme = predefinedThemes[curSelectedThemeId]!!
+ curTextColor = getColor(theme.textColorId)
+ curBackgroundColor = getColor(theme.backgroundColorId)
+ curPrimaryColor = getColor(theme.primaryColorId)
+ curAppIconColor = getColor(theme.appIconColorId)
+ setTheme(getThemeId(curPrimaryColor))
+ colorChanged()
+ }
+ }
+
+ hasUnsavedChanges = true
+ invalidateOptionsMenu()
+ updateTextColors(customization_holder, curTextColor)
+ updateBackgroundColor(curBackgroundColor)
+ updateActionbarColor(curPrimaryColor)
+ }
+
+ private fun getCurrentThemeId(): Int {
+ if (baseConfig.isUsingSharedTheme) {
+ return THEME_SHARED
+ }
+
+ var themeId = THEME_CUSTOM
+ resources.apply {
+ for ((key, value) in predefinedThemes.filter { it.key != THEME_CUSTOM && it.key != THEME_SHARED }) {
+ if (curTextColor == getColor(value.textColorId) &&
+ curBackgroundColor == getColor(value.backgroundColorId) &&
+ curPrimaryColor == getColor(value.primaryColorId) &&
+ curAppIconColor == getColor(value.appIconColorId)
+ ) {
+ themeId = key
+ }
+ }
+ }
+ return themeId
+ }
+
+ private fun getThemeText(): String {
+ var nameId = R.string.custom
+ for ((key, value) in predefinedThemes) {
+ if (key == curSelectedThemeId) {
+ nameId = value.nameId
+ }
+ }
+ return getString(nameId)
+ }
+
+ private fun promptSaveDiscard() {
+ ConfirmationAdvancedDialog(this, "", R.string.save_before_closing, R.string.save, R.string.discard) {
+ if (it) {
+ saveChanges(true)
+ } else {
+ resetColors()
+ finish()
+ }
+ }
+ }
+
+ private fun saveChanges(finishAfterSave: Boolean) {
+ val didAppIconColorChange = curAppIconColor != originalAppIconColor
+ baseConfig.apply {
+ textColor = curTextColor
+ backgroundColor = curBackgroundColor
+ primaryColor = curPrimaryColor
+ appIconColor = curAppIconColor
+ }
+
+ if (didAppIconColorChange) {
+ checkAppIconColor()
+ }
+
+ if (curSelectedThemeId == THEME_SHARED) {
+ val newSharedTheme = SharedTheme(curTextColor, curBackgroundColor, curPrimaryColor, curAppIconColor)
+ updateSharedTheme(newSharedTheme)
+ Intent().apply {
+ action = MyContentProvider.SHARED_THEME_UPDATED
+ sendBroadcast(this)
+ }
+ }
+
+ baseConfig.isUsingSharedTheme = curSelectedThemeId == THEME_SHARED
+ hasUnsavedChanges = false
+ if (finishAfterSave) {
+ finish()
+ } else {
+ invalidateOptionsMenu()
+ }
+ }
+
+ private fun resetColors() {
+ hasUnsavedChanges = false
+ invalidateOptionsMenu()
+ initColorVariables()
+ setupColorsPickers()
+ updateBackgroundColor()
+ updateActionbarColor()
+ invalidateOptionsMenu()
+ updateTextColors(customization_holder)
+ }
+
+ private fun initColorVariables() {
+ curTextColor = baseConfig.textColor
+ curBackgroundColor = baseConfig.backgroundColor
+ curPrimaryColor = baseConfig.primaryColor
+ curAppIconColor = baseConfig.appIconColor
+ }
+
+ private fun setupColorsPickers() {
+ customization_text_color.setFillWithStroke(curTextColor, curBackgroundColor)
+ customization_primary_color.setFillWithStroke(curPrimaryColor, curBackgroundColor)
+ customization_background_color.setFillWithStroke(curBackgroundColor, curBackgroundColor)
+ customization_app_icon_color.setFillWithStroke(curAppIconColor, curBackgroundColor)
+
+ customization_text_color_holder.setOnClickListener { pickTextColor() }
+ customization_background_color_holder.setOnClickListener { pickBackgroundColor() }
+ customization_primary_color_holder.setOnClickListener { pickPrimaryColor() }
+ customization_app_icon_color_holder.setOnClickListener { pickAppIconColor() }
+ apply_to_all_holder.setOnClickListener { applyToAll() }
+ }
+
+ private fun hasColorChanged(old: Int, new: Int) = Math.abs(old - new) > 1
+
+ private fun colorChanged() {
+ hasUnsavedChanges = true
+ setupColorsPickers()
+ invalidateOptionsMenu()
+ }
+
+ private fun setCurrentTextColor(color: Int) {
+ curTextColor = color
+ updateTextColors(customization_holder, color)
+ }
+
+ private fun setCurrentBackgroundColor(color: Int) {
+ curBackgroundColor = color
+ updateBackgroundColor(color)
+ }
+
+ private fun setCurrentPrimaryColor(color: Int) {
+ curPrimaryColor = color
+ updateActionbarColor(color)
+ }
+
+ private fun pickTextColor() {
+ ColorPickerDialog(this, curTextColor) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ if (hasColorChanged(curTextColor, color)) {
+ setCurrentTextColor(color)
+ colorChanged()
+ updateColorTheme(getUpdatedTheme())
+ }
+ }
+ }
+ }
+
+ private fun pickBackgroundColor() {
+ ColorPickerDialog(this, curBackgroundColor) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ if (hasColorChanged(curBackgroundColor, color)) {
+ setCurrentBackgroundColor(color)
+ colorChanged()
+ updateColorTheme(getUpdatedTheme())
+ }
+ }
+ }
+ }
+
+ private fun pickPrimaryColor() {
+ curPrimaryLineColorPicker = LineColorPickerDialog(this, curPrimaryColor, true) { wasPositivePressed, color ->
+ curPrimaryLineColorPicker = null
+ if (wasPositivePressed) {
+ if (hasColorChanged(curPrimaryColor, color)) {
+ setCurrentPrimaryColor(color)
+ colorChanged()
+ updateColorTheme(getUpdatedTheme())
+ setTheme(getThemeId(color))
+ }
+ } else {
+ updateActionbarColor(curPrimaryColor)
+ setTheme(getThemeId(curPrimaryColor))
+ }
+ }
+ }
+
+ private fun pickAppIconColor() {
+ LineColorPickerDialog(this, curAppIconColor, false, R.array.md_app_icon_colors, getAppIconIDs()) { wasPositivePressed, color ->
+ if (wasPositivePressed) {
+ if (hasColorChanged(curAppIconColor, color)) {
+ curAppIconColor = color
+ colorChanged()
+ updateColorTheme(getUpdatedTheme())
+ }
+ }
+ }
+ }
+
+ private fun getUpdatedTheme() = if (curSelectedThemeId == THEME_SHARED) THEME_SHARED else THEME_CUSTOM
+
+ private fun applyToAll() {
+ if (isThankYouInstalled()) {
+ ConfirmationDialog(this, "", R.string.share_colors_success, R.string.ok, 0) {
+ Intent().apply {
+ action = MyContentProvider.SHARED_THEME_ACTIVATED
+ sendBroadcast(this)
+ }
+
+ if (!predefinedThemes.containsKey(THEME_SHARED)) {
+ predefinedThemes[THEME_SHARED] = MyTheme(R.string.shared, 0, 0, 0, 0)
+ }
+ baseConfig.wasSharedThemeEverActivated = true
+ apply_to_all_holder.beGone()
+ updateColorTheme(THEME_SHARED)
+ saveChanges(false)
+ }
+ } else {
+ PurchaseThankYouDialog(this)
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/activities/FAQActivity.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/FAQActivity.kt
new file mode 100644
index 0000000..60bcc28
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/FAQActivity.kt
@@ -0,0 +1,48 @@
+package com.simplemobiletools.commons.activities
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.baseConfig
+import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor
+import com.simplemobiletools.commons.extensions.underlineText
+import com.simplemobiletools.commons.helpers.APP_FAQ
+import com.simplemobiletools.commons.helpers.APP_ICON_IDS
+import com.simplemobiletools.commons.helpers.APP_LAUNCHER_NAME
+import com.simplemobiletools.commons.models.FAQItem
+import kotlinx.android.synthetic.main.activity_faq.*
+import kotlinx.android.synthetic.main.license_faq_item.view.*
+import java.util.*
+
+class FAQActivity : BaseSimpleActivity() {
+ override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList()
+
+ override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: ""
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_faq)
+
+ val titleColor = getAdjustedPrimaryColor()
+ val textColor = baseConfig.textColor
+
+ val inflater = LayoutInflater.from(this)
+ val faqItems = intent.getSerializableExtra(APP_FAQ) as ArrayList
+ faqItems.forEach {
+ val faqItem = it
+ inflater.inflate(R.layout.license_faq_item, null).apply {
+ license_faq_title.apply {
+ text = if (faqItem.title is Int) getString(faqItem.title) else faqItem.title as String
+ underlineText()
+ setTextColor(titleColor)
+ }
+
+ license_faq_text.apply {
+ text = if (faqItem.text is Int) getString(faqItem.text) else faqItem.text as String
+ setTextColor(textColor)
+ }
+ faq_holder.addView(this)
+ }
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/activities/LicenseActivity.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/LicenseActivity.kt
new file mode 100644
index 0000000..d54228c
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/activities/LicenseActivity.kt
@@ -0,0 +1,74 @@
+package com.simplemobiletools.commons.activities
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.*
+import com.simplemobiletools.commons.models.License
+import kotlinx.android.synthetic.main.activity_license.*
+import kotlinx.android.synthetic.main.license_faq_item.view.*
+import java.util.*
+
+class LicenseActivity : BaseSimpleActivity() {
+ override fun getAppIconIDs() = intent.getIntegerArrayListExtra(APP_ICON_IDS) ?: ArrayList()
+
+ override fun getAppLauncherName() = intent.getStringExtra(APP_LAUNCHER_NAME) ?: ""
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_license)
+
+ val linkColor = getAdjustedPrimaryColor()
+ val textColor = baseConfig.textColor
+ updateTextColors(licenses_holder)
+
+ val inflater = LayoutInflater.from(this)
+ val licenses = initLicenses()
+ val licenseMask = intent.getIntExtra(APP_LICENSES, 0) or LICENSE_KOTLIN
+ licenses.filter { licenseMask and it.id != 0 }.forEach {
+ val license = it
+ inflater.inflate(R.layout.license_faq_item, null).apply {
+ license_faq_title.apply {
+ text = getString(license.titleId)
+ underlineText()
+ setTextColor(linkColor)
+ setOnClickListener {
+ launchViewIntent(license.urlId)
+ }
+ }
+
+ license_faq_text.text = getString(license.textId)
+ license_faq_text.setTextColor(textColor)
+ licenses_holder.addView(this)
+ }
+ }
+ }
+
+ private fun initLicenses() = arrayOf(
+ License(LICENSE_KOTLIN, R.string.kotlin_title, R.string.kotlin_text, R.string.kotlin_url),
+ License(LICENSE_SUBSAMPLING, R.string.subsampling_title, R.string.subsampling_text, R.string.subsampling_url),
+ License(LICENSE_GLIDE, R.string.glide_title, R.string.glide_text, R.string.glide_url),
+ License(LICENSE_CROPPER, R.string.cropper_title, R.string.cropper_text, R.string.cropper_url),
+ License(LICENSE_MULTISELECT, R.string.multiselect_title, R.string.multiselect_text, R.string.multiselect_url),
+ License(LICENSE_RTL, R.string.rtl_viewpager_title, R.string.rtl_viewpager_text, R.string.rtl_viewpager_url),
+ License(LICENSE_JODA, R.string.joda_title, R.string.joda_text, R.string.joda_url),
+ License(LICENSE_STETHO, R.string.stetho_title, R.string.stetho_text, R.string.stetho_url),
+ License(LICENSE_OTTO, R.string.otto_title, R.string.otto_text, R.string.otto_url),
+ License(LICENSE_PHOTOVIEW, R.string.photoview_title, R.string.photoview_text, R.string.photoview_url),
+ License(LICENSE_PICASSO, R.string.picasso_title, R.string.picasso_text, R.string.picasso_url),
+ License(LICENSE_PATTERN, R.string.pattern_title, R.string.pattern_text, R.string.pattern_url),
+ License(LICENSE_REPRINT, R.string.reprint_title, R.string.reprint_text, R.string.reprint_url),
+ License(LICENSE_GIF_DRAWABLE, R.string.gif_drawable_title, R.string.gif_drawable_text, R.string.gif_drawable_url),
+ License(LICENSE_AUTOFITTEXTVIEW, R.string.autofittextview_title, R.string.autofittextview_text, R.string.autofittextview_url),
+ License(LICENSE_ROBOLECTRIC, R.string.robolectric_title, R.string.robolectric_text, R.string.robolectric_url),
+ License(LICENSE_ESPRESSO, R.string.espresso_title, R.string.espresso_text, R.string.espresso_url),
+ License(LICENSE_GSON, R.string.gson_title, R.string.gson_text, R.string.gson_url),
+ License(LICENSE_LEAK_CANARY, R.string.leak_canary_title, R.string.leakcanary_text, R.string.leakcanary_url),
+ License(LICENSE_NUMBER_PICKER, R.string.number_picker_title, R.string.number_picker_text, R.string.number_picker_url),
+ License(LICENSE_EXOPLAYER, R.string.exoplayer_title, R.string.exoplayer_text, R.string.exoplayer_url),
+ License(LICENSE_PANORAMA_VIEW, R.string.panorama_view_title, R.string.panorama_view_text, R.string.panorama_view_url),
+ License(LICENSE_SANSELAN, R.string.sanselan_title, R.string.sanselan_text, R.string.sanselan_url),
+ License(LICENSE_FILTERS, R.string.filters_title, R.string.filters_text, R.string.filters_url)
+ )
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/adapters/FilepickerItemsAdapter.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/adapters/FilepickerItemsAdapter.kt
new file mode 100644
index 0000000..7a870b8
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/adapters/FilepickerItemsAdapter.kt
@@ -0,0 +1,107 @@
+package com.simplemobiletools.commons.adapters
+
+import android.content.pm.PackageManager
+import android.view.Menu
+import android.view.View
+import android.view.ViewGroup
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade
+import com.bumptech.glide.request.RequestOptions
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.OTG_PATH
+import com.simplemobiletools.commons.models.FileDirItem
+import com.simplemobiletools.commons.views.MyRecyclerView
+import kotlinx.android.synthetic.main.filepicker_list_item.view.*
+
+class FilepickerItemsAdapter(activity: BaseSimpleActivity, val fileDirItems: List, recyclerView: MyRecyclerView,
+ itemClick: (Any) -> Unit) : MyRecyclerViewAdapter(activity, recyclerView, null, itemClick) {
+
+ private val folderDrawable = activity.resources.getColoredDrawableWithColor(R.drawable.ic_folder, textColor)
+ private val fileDrawable = activity.resources.getColoredDrawableWithColor(R.drawable.ic_file, textColor)
+ private val hasOTGConnected = activity.hasOTGConnected()
+
+ init {
+ folderDrawable.alpha = 180
+ fileDrawable.alpha = 180
+ }
+
+ override fun getActionMenuId() = 0
+
+ override fun prepareItemSelection(viewHolder: ViewHolder) {}
+
+ override fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?) {}
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = createViewHolder(R.layout.filepicker_list_item, parent)
+
+ override fun onBindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int) {
+ val fileDirItem = fileDirItems[position]
+ val view = holder.bindView(fileDirItem, true, false) { itemView, adapterPosition ->
+ setupView(itemView, fileDirItem)
+ }
+ bindViewHolder(holder, position, view)
+ }
+
+ override fun getItemCount() = fileDirItems.size
+
+ override fun prepareActionMode(menu: Menu) {}
+
+ override fun actionItemPressed(id: Int) {}
+
+ override fun getSelectableItemCount() = fileDirItems.size
+
+ override fun getIsItemSelectable(position: Int) = false
+
+ override fun onViewRecycled(holder: MyRecyclerViewAdapter.ViewHolder) {
+ super.onViewRecycled(holder)
+ if (!activity.isActivityDestroyed()) {
+ Glide.with(activity).clear(holder.itemView?.list_item_icon!!)
+ }
+ }
+
+ private fun setupView(view: View, fileDirItem: FileDirItem) {
+ view.apply {
+ list_item_name.text = fileDirItem.name
+ list_item_name.setTextColor(textColor)
+ list_item_details.setTextColor(textColor)
+
+ if (fileDirItem.isDirectory) {
+ list_item_icon.setImageDrawable(folderDrawable)
+ list_item_details.text = getChildrenCnt(fileDirItem)
+ } else {
+ list_item_details.text = fileDirItem.size.formatSize()
+ val path = fileDirItem.path
+ val options = RequestOptions()
+ .centerCrop()
+ .error(fileDrawable)
+
+ var itemToLoad = if (fileDirItem.name.endsWith(".apk", true)) {
+ val packageInfo = context.packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES)
+ if (packageInfo != null) {
+ val appInfo = packageInfo.applicationInfo
+ appInfo.sourceDir = path
+ appInfo.publicSourceDir = path
+ appInfo.loadIcon(context.packageManager)
+ } else {
+ path
+ }
+ } else {
+ path
+ }
+
+ if (!activity.isActivityDestroyed()) {
+ if (hasOTGConnected && itemToLoad is String && itemToLoad.startsWith(OTG_PATH)) {
+ itemToLoad = itemToLoad.getOTGPublicPath(activity)
+ }
+ Glide.with(activity).load(itemToLoad).transition(withCrossFade()).apply(options).into(list_item_icon)
+ }
+ }
+ }
+ }
+
+ private fun getChildrenCnt(item: FileDirItem): String {
+ val children = item.children
+ return activity.resources.getQuantityString(R.plurals.items, children, children)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/adapters/MyArrayAdapter.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/adapters/MyArrayAdapter.kt
new file mode 100644
index 0000000..8c80b9a
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/adapters/MyArrayAdapter.kt
@@ -0,0 +1,23 @@
+package com.simplemobiletools.commons.adapters
+
+import android.content.Context
+import android.graphics.drawable.ColorDrawable
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.TextView
+
+class MyArrayAdapter(context: Context, res: Int, items: Array, val textColor: Int, val backgroundColor: Int, val padding: Int) :
+ ArrayAdapter(context, res, items) {
+ override fun getDropDownView(position: Int, convertView: View?, parent: ViewGroup?): View {
+ val view = super.getView(position, convertView, parent)
+
+ view.findViewById(android.R.id.text1).apply {
+ setTextColor(textColor)
+ setPadding(padding, padding, padding, padding)
+ background = ColorDrawable(backgroundColor)
+ }
+
+ return view
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/adapters/MyRecyclerViewAdapter.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/adapters/MyRecyclerViewAdapter.kt
new file mode 100644
index 0000000..1b31517
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/adapters/MyRecyclerViewAdapter.kt
@@ -0,0 +1,338 @@
+package com.simplemobiletools.commons.adapters
+
+import android.support.v7.view.ActionMode
+import android.support.v7.widget.RecyclerView
+import android.util.SparseArray
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import com.bignerdranch.android.multiselector.ModalMultiSelectorCallback
+import com.bignerdranch.android.multiselector.MultiSelector
+import com.bignerdranch.android.multiselector.SwappingHolder
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.baseConfig
+import com.simplemobiletools.commons.interfaces.MyAdapterListener
+import com.simplemobiletools.commons.views.FastScroller
+import com.simplemobiletools.commons.views.MyRecyclerView
+import java.util.*
+
+abstract class MyRecyclerViewAdapter(val activity: BaseSimpleActivity, val recyclerView: MyRecyclerView, val fastScroller: FastScroller? = null,
+ val itemClick: (Any) -> Unit) : RecyclerView.Adapter() {
+ protected val baseConfig = activity.baseConfig
+ protected val resources = activity.resources!!
+ protected val layoutInflater = activity.layoutInflater
+ protected var primaryColor = baseConfig.primaryColor
+ protected var textColor = baseConfig.textColor
+ protected var backgroundColor = baseConfig.backgroundColor
+ protected var viewHolders = SparseArray()
+ protected val selectedPositions = HashSet()
+ protected var positionOffset = 0
+
+ private val multiSelector = MultiSelector()
+ private var actMode: ActionMode? = null
+ private var actBarTextView: TextView? = null
+ private var lastLongPressedItem = -1
+
+ abstract fun getActionMenuId(): Int
+
+ abstract fun prepareItemSelection(viewHolder: ViewHolder)
+
+ abstract fun markViewHolderSelection(select: Boolean, viewHolder: ViewHolder?)
+
+ abstract fun prepareActionMode(menu: Menu)
+
+ abstract fun actionItemPressed(id: Int)
+
+ abstract fun getSelectableItemCount(): Int
+
+ abstract fun getIsItemSelectable(position: Int): Boolean
+
+ protected fun isOneItemSelected() = selectedPositions.size == 1
+
+ init {
+ fastScroller?.resetScrollPositions()
+ }
+
+ protected fun toggleItemSelection(select: Boolean, pos: Int) {
+ if (select && !getIsItemSelectable(pos)) {
+ return
+ }
+
+ if (select) {
+ if (viewHolders[pos] != null) {
+ prepareItemSelection(viewHolders[pos])
+ }
+ selectedPositions.add(pos)
+ } else {
+ selectedPositions.remove(pos)
+ }
+
+ markViewHolderSelection(select, viewHolders[pos])
+
+ if (selectedPositions.isEmpty()) {
+ finishActMode()
+ return
+ }
+
+ updateTitle(selectedPositions.size)
+ }
+
+ private fun updateTitle(cnt: Int) {
+ val selectableItemCount = getSelectableItemCount()
+ val selectedCount = Math.min(cnt, selectableItemCount)
+ val oldTitle = actBarTextView?.text
+ val newTitle = "$selectedCount / $selectableItemCount"
+ if (oldTitle != newTitle) {
+ actBarTextView?.text = newTitle
+ actMode?.invalidate()
+ }
+ }
+
+ protected fun selectAll() {
+ val cnt = itemCount - positionOffset
+ for (i in 0 until cnt) {
+ if (getIsItemSelectable(i)) {
+ selectedPositions.add(i)
+ notifyItemChanged(i + positionOffset)
+ }
+ }
+ updateTitle(cnt)
+ lastLongPressedItem = -1
+ }
+
+ protected fun setupDragListener(enable: Boolean) {
+ if (enable) {
+ recyclerView.setupDragListener(object : MyRecyclerView.MyDragListener {
+ override fun selectItem(position: Int) {
+ selectItemPosition(position)
+ }
+
+ override fun selectRange(initialSelection: Int, lastDraggedIndex: Int, minReached: Int, maxReached: Int) {
+ selectItemRange(initialSelection, lastDraggedIndex - positionOffset, minReached, maxReached)
+ }
+ })
+ } else {
+ recyclerView.setupDragListener(null)
+ }
+ }
+
+ fun setupZoomListener(zoomListener: MyRecyclerView.MyZoomListener?) {
+ recyclerView.setupZoomListener(zoomListener)
+ }
+
+ fun addVerticalDividers(add: Boolean) {
+ /* if (recyclerView.itemDecorationCount > 0) {
+ recyclerView.removeItemDecorationAt(0)
+ }
+
+ if (add) {
+ DividerItemDecoration(activity, DividerItemDecoration.VERTICAL).apply {
+ setDrawable(resources.getDrawable(R.drawable.divider))
+ recyclerView.addItemDecoration(this)
+ }
+ }*/
+ }
+
+ protected fun selectItemPosition(pos: Int) {
+ toggleItemSelection(true, pos)
+ }
+
+ protected fun selectItemRange(from: Int, to: Int, min: Int, max: Int) {
+ if (from == to) {
+ (min..max).filter { it != from }.forEach { toggleItemSelection(false, it) }
+ return
+ }
+
+ if (to < from) {
+ for (i in to..from) {
+ toggleItemSelection(true, i)
+ }
+
+ if (min > -1 && min < to) {
+ (min until to).filter { it != from }.forEach { toggleItemSelection(false, it) }
+ }
+
+ if (max > -1) {
+ for (i in from + 1..max) {
+ toggleItemSelection(false, i)
+ }
+ }
+ } else {
+ for (i in from..to) {
+ toggleItemSelection(true, i)
+ }
+
+ if (max > -1 && max > to) {
+ (to + 1..max).filter { it != from }.forEach { toggleItemSelection(false, it) }
+ }
+
+ if (min > -1) {
+ for (i in min until from) {
+ toggleItemSelection(false, i)
+ }
+ }
+ }
+ }
+
+ fun finishActMode() {
+ actMode?.finish()
+ }
+
+ fun updateTextColor(textColor: Int) {
+ this.textColor = textColor
+ notifyDataSetChanged()
+ }
+
+ fun updatePrimaryColor(primaryColor: Int) {
+ this.primaryColor = primaryColor
+ }
+
+ fun updateBackgroundColor(backgroundColor: Int) {
+ this.backgroundColor = backgroundColor
+ }
+
+ private val adapterListener = object : MyAdapterListener {
+ override fun toggleItemSelectionAdapter(select: Boolean, position: Int) {
+ toggleItemSelection(select, position)
+ lastLongPressedItem = -1
+ }
+
+ override fun getSelectedPositions() = selectedPositions
+
+ override fun itemLongClicked(position: Int) {
+ recyclerView.setDragSelectActive(position)
+ lastLongPressedItem = if (lastLongPressedItem == -1) {
+ position
+ } else {
+ val min = Math.min(lastLongPressedItem, position)
+ val max = Math.max(lastLongPressedItem, position)
+ for (i in min..max) {
+ toggleItemSelection(true, i)
+ }
+ -1
+ }
+ }
+ }
+
+ private val multiSelectorMode = object : ModalMultiSelectorCallback(multiSelector) {
+ override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean {
+ actionItemPressed(item.itemId)
+ return true
+ }
+
+ override fun onCreateActionMode(actionMode: ActionMode?, menu: Menu?): Boolean {
+ super.onCreateActionMode(actionMode, menu)
+ actMode = actionMode
+ actBarTextView = layoutInflater.inflate(R.layout.actionbar_title, null) as TextView
+ actMode!!.customView = actBarTextView
+ actBarTextView!!.setOnClickListener {
+ if (getSelectableItemCount() == selectedPositions.size) {
+ finishActMode()
+ } else {
+ selectAll()
+ }
+ }
+ activity.menuInflater.inflate(getActionMenuId(), menu)
+ return true
+ }
+
+ override fun onPrepareActionMode(actionMode: ActionMode?, menu: Menu): Boolean {
+ prepareActionMode(menu)
+ return true
+ }
+
+ override fun onDestroyActionMode(actionMode: ActionMode?) {
+ super.onDestroyActionMode(actionMode)
+ selectedPositions.forEach {
+ markViewHolderSelection(false, viewHolders[it])
+ }
+ selectedPositions.clear()
+ actBarTextView?.text = ""
+ actMode = null
+ lastLongPressedItem = -1
+ }
+ }
+
+ protected fun createViewHolder(layoutType: Int, parent: ViewGroup?): ViewHolder {
+ val view = layoutInflater.inflate(layoutType, parent, false)
+ return ViewHolder(view, adapterListener, activity, multiSelectorMode, multiSelector, positionOffset, itemClick)
+ }
+
+ protected fun bindViewHolder(holder: MyRecyclerViewAdapter.ViewHolder, position: Int, view: View) {
+ viewHolders.put(position, holder)
+ toggleItemSelection(selectedPositions.contains(position), position)
+ holder.itemView.tag = holder
+ }
+
+ override fun onViewRecycled(holder: ViewHolder) {
+ super.onViewRecycled(holder)
+ val pos = viewHolders.indexOfValue(holder)
+ try {
+ if (pos != -1) {
+ viewHolders.removeAt(pos)
+ }
+ } catch (ignored: ArrayIndexOutOfBoundsException) {
+ }
+ }
+
+ protected fun removeSelectedItems() {
+ val newViewHolders = SparseArray()
+ val cnt = viewHolders.size()
+ for (i in 0..cnt) {
+ if (selectedPositions.contains(i)) {
+ continue
+ }
+
+ val view = viewHolders.get(i, null)
+ val newIndex = i - selectedPositions.count { it <= i }
+ newViewHolders.put(newIndex, view)
+ }
+ viewHolders = newViewHolders
+
+ selectedPositions.sortedDescending().forEach {
+ notifyItemRemoved(it + positionOffset)
+ }
+
+ finishActMode()
+ fastScroller?.measureRecyclerView()
+ }
+
+ open class ViewHolder(view: View, val adapterListener: MyAdapterListener? = null, val activity: BaseSimpleActivity? = null,
+ val multiSelectorCallback: ModalMultiSelectorCallback? = null, val multiSelector: MultiSelector,
+ val positionOffset: Int = 0, val itemClick: ((Any) -> (Unit))? = null) : SwappingHolder(view, multiSelector) {
+ fun bindView(any: Any, allowSingleClick: Boolean, allowLongClick: Boolean, callback: (itemView: View, adapterPosition: Int) -> Unit): View {
+ return itemView.apply {
+ callback(this, adapterPosition)
+
+ if (allowSingleClick) {
+ setOnClickListener { viewClicked(any) }
+ setOnLongClickListener { if (allowLongClick) viewLongClicked() else viewClicked(any); true }
+ } else {
+ setOnClickListener(null)
+ setOnLongClickListener(null)
+ }
+ }
+ }
+
+ private fun viewClicked(any: Any) {
+ if (multiSelector.isSelectable) {
+ val isSelected = adapterListener?.getSelectedPositions()?.contains(adapterPosition - positionOffset) ?: false
+ adapterListener?.toggleItemSelectionAdapter(!isSelected, adapterPosition - positionOffset)
+ } else {
+ itemClick?.invoke(any)
+ }
+ }
+
+ private fun viewLongClicked() {
+ if (!multiSelector.isSelectable && multiSelectorCallback != null) {
+ activity?.startSupportActionMode(multiSelectorCallback)
+ adapterListener?.toggleItemSelectionAdapter(true, adapterPosition - positionOffset)
+ }
+
+ adapterListener?.itemLongClicked(adapterPosition - positionOffset)
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/asynctasks/CopyMoveTask.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/asynctasks/CopyMoveTask.kt
new file mode 100644
index 0000000..3c67599
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/asynctasks/CopyMoveTask.kt
@@ -0,0 +1,286 @@
+package com.simplemobiletools.commons.asynctasks
+
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.ContentValues
+import android.content.Context
+import android.os.AsyncTask
+import android.os.Handler
+import android.provider.MediaStore
+import android.support.v4.app.NotificationCompat
+import android.support.v4.provider.DocumentFile
+import android.support.v4.util.Pair
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.CONFLICT_OVERWRITE
+import com.simplemobiletools.commons.helpers.CONFLICT_SKIP
+import com.simplemobiletools.commons.helpers.OTG_PATH
+import com.simplemobiletools.commons.helpers.isOreoPlus
+import com.simplemobiletools.commons.interfaces.CopyMoveListener
+import com.simplemobiletools.commons.models.FileDirItem
+import java.io.File
+import java.io.InputStream
+import java.io.OutputStream
+import java.lang.ref.WeakReference
+import java.util.*
+
+class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = false, val copyMediaOnly: Boolean, val conflictResolutions: LinkedHashMap,
+ listener: CopyMoveListener, val copyHidden: Boolean) : AsyncTask, String>, Void, Boolean>() {
+ private val INITIAL_PROGRESS_DELAY = 3000L
+ private val PROGRESS_RECHECK_INTERVAL = 500L
+
+ private var mListener: WeakReference? = null
+ private var mTransferredFiles = ArrayList()
+ private var mDocuments = LinkedHashMap()
+ private var mFiles = ArrayList()
+ private var mFileCountToCopy = 0
+ private var mDestinationPath = ""
+
+ // progress indication
+ private var mNotificationManager: NotificationManager
+ private var mNotificationBuilder: NotificationCompat.Builder
+ private var mCurrFilename = ""
+ private var mCurrentProgress = 0L
+ private var mMaxSize = 0
+ private var mNotifId = 0
+ private var mProgressHandler = Handler()
+
+ init {
+ mListener = WeakReference(listener)
+ mNotificationManager = activity.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ mNotificationBuilder = NotificationCompat.Builder(activity)
+ }
+
+ override fun doInBackground(vararg params: Pair, String>): Boolean? {
+ if (params.isEmpty()) {
+ return false
+ }
+
+ val pair = params[0]
+ mFiles = pair.first!!
+ mDestinationPath = pair.second!!
+ mFileCountToCopy = mFiles.size
+ mNotifId = (System.currentTimeMillis() / 1000).toInt()
+ mMaxSize = 0
+ for (file in mFiles) {
+ if (file.size == 0L) {
+ file.size = file.getProperSize(activity, copyHidden)
+ }
+ val newPath = "$mDestinationPath/${file.name}"
+ val fileExists = if (newPath.startsWith(OTG_PATH)) activity.getOTGFastDocumentFile(newPath)?.exists()
+ ?: false else File(newPath).exists()
+ if (getConflictResolution(newPath) != CONFLICT_SKIP || !fileExists) {
+ mMaxSize += (file.size / 1000).toInt()
+ }
+ }
+
+ mProgressHandler.postDelayed({
+ initProgressNotification()
+ updateProgress()
+ }, INITIAL_PROGRESS_DELAY)
+
+ for (file in mFiles) {
+ try {
+ val newPath = "$mDestinationPath/${file.name}"
+ val newFileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath(), file.isDirectory)
+ if (activity.getDoesFilePathExist(newPath)) {
+ val resolution = getConflictResolution(newPath)
+ if (resolution == CONFLICT_SKIP) {
+ mFileCountToCopy--
+ continue
+ } else if (resolution == CONFLICT_OVERWRITE) {
+ newFileDirItem.isDirectory = if (File(newPath).exists()) File(newPath).isDirectory else activity.getSomeDocumentFile(newPath)!!.isDirectory
+ activity.deleteFileBg(newFileDirItem, true)
+ }
+ }
+
+ copy(file, newFileDirItem)
+ } catch (e: Exception) {
+ activity.toast(e.toString())
+ return false
+ }
+ }
+
+ if (!copyOnly) {
+ activity.deleteFilesBg(mTransferredFiles) {}
+ }
+
+ return true
+ }
+
+ override fun onPostExecute(success: Boolean) {
+ mProgressHandler.removeCallbacksAndMessages(null)
+ mNotificationManager.cancel(mNotifId)
+ val listener = mListener?.get() ?: return
+
+ if (success) {
+ listener.copySucceeded(copyOnly, mTransferredFiles.size >= mFileCountToCopy, mDestinationPath)
+ } else {
+ listener.copyFailed()
+ }
+ }
+
+ private fun initProgressNotification() {
+ val channelId = "copying_moving_channel"
+ val title = activity.getString(if (copyOnly) R.string.copying else R.string.moving)
+ if (isOreoPlus()) {
+ val importance = NotificationManager.IMPORTANCE_LOW
+ NotificationChannel(channelId, title, importance).apply {
+ enableLights(false)
+ enableVibration(false)
+ mNotificationManager.createNotificationChannel(this)
+ }
+ }
+
+ mNotificationBuilder.setContentTitle(title)
+ .setSmallIcon(R.drawable.ic_copy)
+ .setChannelId(channelId)
+ }
+
+ private fun updateProgress() {
+ mNotificationBuilder.apply {
+ setContentText(mCurrFilename)
+ setProgress(mMaxSize, (mCurrentProgress / 1000).toInt(), false)
+ mNotificationManager.notify(mNotifId, build())
+ }
+
+ mProgressHandler.removeCallbacksAndMessages(null)
+ mProgressHandler.postDelayed({
+ updateProgress()
+ }, PROGRESS_RECHECK_INTERVAL)
+ }
+
+ private fun getConflictResolution(path: String): Int {
+ return if (conflictResolutions.size == 1 && conflictResolutions.containsKey("")) {
+ conflictResolutions[""]!!
+ } else if (conflictResolutions.containsKey(path)) {
+ conflictResolutions[path]!!
+ } else {
+ CONFLICT_SKIP
+ }
+ }
+
+ private fun copy(source: FileDirItem, destination: FileDirItem) {
+ if (source.isDirectory) {
+ copyDirectory(source, destination.path)
+ } else {
+ copyFile(source, destination)
+ }
+ }
+
+ private fun copyDirectory(source: FileDirItem, destinationPath: String) {
+ if (!activity.createDirectorySync(destinationPath)) {
+ val error = String.format(activity.getString(R.string.could_not_create_folder), destinationPath)
+ activity.showErrorToast(error)
+ return
+ }
+
+ if (source.path.startsWith(OTG_PATH)) {
+ val children = activity.getDocumentFile(source.path)?.listFiles() ?: return
+ for (child in children) {
+ val newPath = "$destinationPath/${child.name}"
+ if (activity.getDoesFilePathExist(newPath)) {
+ continue
+ }
+
+ val oldPath = "${source.path}/${child.name}"
+ val oldFileDirItem = FileDirItem(oldPath, child.name!!, child.isDirectory, 0, child.length())
+ val newFileDirItem = FileDirItem(newPath, child.name!!, child.isDirectory)
+ copy(oldFileDirItem, newFileDirItem)
+ }
+ mTransferredFiles.add(source)
+ } else {
+ val children = File(source.path).list()
+ for (child in children) {
+ val newPath = "$destinationPath/$child"
+ if (activity.getDoesFilePathExist(newPath)) {
+ continue
+ }
+
+ val oldFile = File(source.path, child)
+ val oldFileDirItem = oldFile.toFileDirItem(activity.applicationContext)
+ val newFileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath(), oldFile.isDirectory)
+ copy(oldFileDirItem, newFileDirItem)
+ }
+ mTransferredFiles.add(source)
+ }
+ }
+
+ private fun copyFile(source: FileDirItem, destination: FileDirItem) {
+ if (copyMediaOnly && !source.path.isMediaFile()) {
+ mCurrentProgress += source.size
+ return
+ }
+
+ val directory = destination.getParentPath()
+ if (!activity.createDirectorySync(directory)) {
+ val error = String.format(activity.getString(R.string.could_not_create_folder), directory)
+ activity.showErrorToast(error)
+ mCurrentProgress += source.size
+ return
+ }
+
+ mCurrFilename = source.name
+ var inputStream: InputStream? = null
+ var out: OutputStream? = null
+ try {
+ if (!mDocuments.containsKey(directory) && activity.needsStupidWritePermissions(destination.path)) {
+ mDocuments[directory] = activity.getDocumentFile(directory)
+ }
+ out = activity.getFileOutputStreamSync(destination.path, source.path.getMimeType(), mDocuments[directory])
+ inputStream = activity.getFileInputStreamSync(source.path)!!
+
+ var copiedSize = 0L
+ val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
+ var bytes = inputStream.read(buffer)
+ while (bytes >= 0) {
+ out!!.write(buffer, 0, bytes)
+ copiedSize += bytes
+ mCurrentProgress += bytes
+ bytes = inputStream.read(buffer)
+ }
+
+ if (source.size == copiedSize) {
+ mTransferredFiles.add(source)
+ activity.scanPathRecursively(destination.path) {
+ if (activity.baseConfig.keepLastModified) {
+ copyOldLastModified(source.path, destination.path)
+ }
+ }
+ }
+ } catch (e: Exception) {
+ activity.showErrorToast(e)
+ } finally {
+ inputStream?.close()
+ out?.close()
+ }
+ }
+
+ private fun copyOldLastModified(sourcePath: String, destinationPath: String) {
+ val projection = arrayOf(
+ MediaStore.Images.Media.DATE_TAKEN,
+ MediaStore.Images.Media.DATE_MODIFIED)
+ val uri = MediaStore.Files.getContentUri("external")
+ val selection = "${MediaStore.MediaColumns.DATA} = ?"
+ var selectionArgs = arrayOf(sourcePath)
+ val cursor = activity.applicationContext.contentResolver.query(uri, projection, selection, selectionArgs, null)
+
+ cursor?.use {
+ if (cursor.moveToFirst()) {
+ val dateTaken = cursor.getLongValue(MediaStore.Images.Media.DATE_TAKEN)
+ val dateModified = cursor.getIntValue(MediaStore.Images.Media.DATE_MODIFIED)
+
+ val values = ContentValues().apply {
+ put(MediaStore.Images.Media.DATE_TAKEN, dateTaken)
+ put(MediaStore.Images.Media.DATE_MODIFIED, dateModified)
+ }
+
+ selectionArgs = arrayOf(destinationPath)
+ activity.scanPathRecursively(destinationPath) {
+ activity.applicationContext.contentResolver.update(uri, values, selection, selectionArgs)
+ }
+ }
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/ColorPickerDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/ColorPickerDialog.kt
new file mode 100644
index 0000000..fb661ec
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/ColorPickerDialog.kt
@@ -0,0 +1,198 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.graphics.Color
+import android.support.v7.app.AlertDialog
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.MotionEvent
+import android.view.View
+import android.view.View.OnTouchListener
+import android.view.ViewGroup
+import android.view.WindowManager
+import android.widget.EditText
+import android.widget.ImageView
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.views.ColorPickerSquare
+import kotlinx.android.synthetic.main.dialog_color_picker.view.*
+
+// forked from https://github.com/yukuku/ambilwarna
+class ColorPickerDialog(val activity: Activity, color: Int, val removeDimmedBackground: Boolean = false,
+ val currentColorCallback: ((color: Int) -> Unit)? = null, val callback: (wasPositivePressed: Boolean, color: Int) -> Unit) {
+ lateinit var viewHue: View
+ lateinit var viewSatVal: ColorPickerSquare
+ lateinit var viewCursor: ImageView
+ lateinit var viewNewColor: ImageView
+ lateinit var viewTarget: ImageView
+ lateinit var newHexField: EditText
+ lateinit var viewContainer: ViewGroup
+ private val currentColorHsv = FloatArray(3)
+ private val backgroundColor = activity.baseConfig.backgroundColor
+ private var isHueBeingDragged = false
+ private var wasDimmedBackgroundRemoved = false
+ private var dialog: AlertDialog? = null
+
+ init {
+ Color.colorToHSV(color, currentColorHsv)
+
+ val view = activity.layoutInflater.inflate(R.layout.dialog_color_picker, null).apply {
+ viewHue = color_picker_hue
+ viewSatVal = color_picker_square
+ viewCursor = color_picker_hue_cursor
+
+ viewNewColor = color_picker_new_color
+ viewTarget = color_picker_cursor
+ viewContainer = color_picker_holder
+ newHexField = color_picker_new_hex
+
+ viewSatVal.setHue(getHue())
+ viewNewColor.setFillWithStroke(getColor(), backgroundColor)
+ color_picker_old_color.setFillWithStroke(color, backgroundColor)
+
+ val hexCode = getHexCode(color)
+ color_picker_old_hex.text = "#$hexCode"
+ newHexField.setText(hexCode)
+ }
+
+ viewHue.setOnTouchListener(OnTouchListener { v, event ->
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ isHueBeingDragged = true
+ }
+
+ if (event.action == MotionEvent.ACTION_MOVE || event.action == MotionEvent.ACTION_DOWN || event.action == MotionEvent.ACTION_UP) {
+ var y = event.y
+ if (y < 0f)
+ y = 0f
+
+ if (y > viewHue.measuredHeight) {
+ y = viewHue.measuredHeight - 0.001f // to avoid jumping the cursor from bottom to top.
+ }
+ var hue = 360f - 360f / viewHue.measuredHeight * y
+ if (hue == 360f)
+ hue = 0f
+
+ currentColorHsv[0] = hue
+ updateHue()
+ newHexField.setText(getHexCode(getColor()))
+
+ if (event.action == MotionEvent.ACTION_UP) {
+ isHueBeingDragged = false
+ }
+ return@OnTouchListener true
+ }
+ false
+ })
+
+ viewSatVal.setOnTouchListener(OnTouchListener { v, event ->
+ if (event.action == MotionEvent.ACTION_MOVE || event.action == MotionEvent.ACTION_DOWN || event.action == MotionEvent.ACTION_UP) {
+ var x = event.x
+ var y = event.y
+
+ if (x < 0f)
+ x = 0f
+ if (x > viewSatVal.measuredWidth)
+ x = viewSatVal.measuredWidth.toFloat()
+ if (y < 0f)
+ y = 0f
+ if (y > viewSatVal.measuredHeight)
+ y = viewSatVal.measuredHeight.toFloat()
+
+ currentColorHsv[1] = 1f / viewSatVal.measuredWidth * x
+ currentColorHsv[2] = 1f - 1f / viewSatVal.measuredHeight * y
+
+ moveColorPicker()
+ viewNewColor.setFillWithStroke(getColor(), backgroundColor)
+ newHexField.setText(getHexCode(getColor()))
+ return@OnTouchListener true
+ }
+ false
+ })
+
+ newHexField.addTextChangedListener(object : TextWatcher {
+ override fun afterTextChanged(s: Editable?) {
+ }
+
+ override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
+ }
+
+ override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
+ if (s.length == 6 && !isHueBeingDragged) {
+ try {
+ val newColor = Color.parseColor("#$s")
+ Color.colorToHSV(newColor, currentColorHsv)
+ updateHue()
+ moveColorPicker()
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+ })
+
+ val textColor = activity.baseConfig.textColor
+ dialog = AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok) { dialog, which -> confirmNewColor() }
+ .setNegativeButton(R.string.cancel) { dialog, which -> dialogDismissed() }
+ .setOnCancelListener { dialogDismissed() }
+ .create().apply {
+ activity.setupDialogStuff(view, this) {
+ view.color_picker_arrow.applyColorFilter(textColor)
+ view.color_picker_hex_arrow.applyColorFilter(textColor)
+ viewCursor.applyColorFilter(textColor)
+ }
+ }
+
+ view.onGlobalLayout {
+ moveHuePicker()
+ moveColorPicker()
+ }
+ }
+
+ private fun dialogDismissed() {
+ callback(false, 0)
+ }
+
+ private fun confirmNewColor() {
+ val hexValue = newHexField.value
+ if (hexValue.length == 6) {
+ callback(true, Color.parseColor("#$hexValue"))
+ } else {
+ callback(true, getColor())
+ }
+ }
+
+ private fun getHexCode(color: Int) = color.toHex().substring(1)
+
+ private fun updateHue() {
+ viewSatVal.setHue(getHue())
+ moveHuePicker()
+ viewNewColor.setFillWithStroke(getColor(), backgroundColor)
+ if (removeDimmedBackground && !wasDimmedBackgroundRemoved) {
+ dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ wasDimmedBackgroundRemoved = true
+ }
+
+ currentColorCallback?.invoke(getColor())
+ }
+
+ private fun moveHuePicker() {
+ var y = viewHue.measuredHeight - getHue() * viewHue.measuredHeight / 360f
+ if (y == viewHue.measuredHeight.toFloat())
+ y = 0f
+
+ viewCursor.x = (viewHue.left - viewCursor.width).toFloat()
+ viewCursor.y = viewHue.top + y - viewCursor.height / 2
+ }
+
+ private fun moveColorPicker() {
+ val x = getSat() * viewSatVal.measuredWidth
+ val y = (1f - getVal()) * viewSatVal.measuredHeight
+ viewTarget.x = viewSatVal.left + x - viewTarget.width / 2
+ viewTarget.y = viewSatVal.top + y - viewTarget.height / 2
+ }
+
+ private fun getColor() = Color.HSVToColor(currentColorHsv)
+ private fun getHue() = currentColorHsv[0]
+ private fun getSat() = currentColorHsv[1]
+ private fun getVal() = currentColorHsv[2]
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/ConfirmationAdvancedDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/ConfirmationAdvancedDialog.kt
new file mode 100644
index 0000000..f68707d
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/ConfirmationAdvancedDialog.kt
@@ -0,0 +1,34 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import kotlinx.android.synthetic.main.dialog_message.view.*
+
+class ConfirmationAdvancedDialog(activity: Activity, message: String = "", messageId: Int = R.string.proceed_with_deletion, positive: Int = R.string.yes,
+ negative: Int, val callback: (result: Boolean) -> Unit) {
+ var dialog: AlertDialog
+
+ init {
+ val view = activity.layoutInflater.inflate(R.layout.dialog_message, null)
+ view.message.text = if (message.isEmpty()) activity.resources.getString(messageId) else message
+
+ dialog = AlertDialog.Builder(activity)
+ .setPositiveButton(positive, { dialog, which -> positivePressed() })
+ .setNegativeButton(negative, { dialog, which -> negativePressed() })
+ .create().apply {
+ activity.setupDialogStuff(view, this)
+ }
+ }
+
+ private fun positivePressed() {
+ dialog.dismiss()
+ callback(true)
+ }
+
+ private fun negativePressed() {
+ dialog.dismiss()
+ callback(false)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/ConfirmationDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/ConfirmationDialog.kt
new file mode 100644
index 0000000..53307d7
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/ConfirmationDialog.kt
@@ -0,0 +1,42 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import kotlinx.android.synthetic.main.dialog_message.view.*
+
+/**
+ * A simple dialog without any view, just a messageId, a positive button and optionally a negative button
+ *
+ * @param activity has to be activity context to avoid some Theme.AppCompat issues
+ * @param message the dialogs message, can be any String. If empty, messageId is used
+ * @param messageId the dialogs messageId ID. Used only if message is empty
+ * @param positive positive buttons text ID
+ * @param negative negative buttons text ID (optional)
+ * @param callback an anonymous function
+ */
+class ConfirmationDialog(activity: Activity, message: String = "", messageId: Int = R.string.proceed_with_deletion, positive: Int = R.string.yes,
+ negative: Int = R.string.no, val callback: () -> Unit) {
+ var dialog: AlertDialog
+
+ init {
+ val view = activity.layoutInflater.inflate(R.layout.dialog_message, null)
+ view.message.text = if (message.isEmpty()) activity.resources.getString(messageId) else message
+
+ val builder = AlertDialog.Builder(activity)
+ .setPositiveButton(positive) { dialog, which -> dialogConfirmed() }
+
+ if (negative != 0)
+ builder.setNegativeButton(negative, null)
+
+ dialog = builder.create().apply {
+ activity.setupDialogStuff(view, this)
+ }
+ }
+
+ private fun dialogConfirmed() {
+ dialog.dismiss()
+ callback()
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/CreateNewFolderDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/CreateNewFolderDialog.kt
new file mode 100644
index 0000000..463af18
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/CreateNewFolderDialog.kt
@@ -0,0 +1,69 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.support.v7.app.AlertDialog
+import android.view.View
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.*
+import kotlinx.android.synthetic.main.dialog_create_new_folder.view.*
+import java.io.File
+
+class CreateNewFolderDialog(val activity: BaseSimpleActivity, val path: String, val callback: (path: String) -> Unit) {
+ init {
+ val view = activity.layoutInflater.inflate(R.layout.dialog_create_new_folder, null)
+ view.folder_path.text = "${activity.humanizePath(path).trimEnd('/')}/"
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, null)
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this, R.string.create_new_folder) {
+ showKeyboard(view.folder_name)
+ getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener {
+ val name = view.folder_name.value
+ when {
+ name.isEmpty() -> activity.toast(R.string.empty_name)
+ name.isAValidFilename() -> {
+ val file = File(path, name)
+ if (file.exists()) {
+ activity.toast(R.string.name_taken)
+ return@OnClickListener
+ }
+
+ createFolder("$path/$name", this)
+ }
+ else -> activity.toast(R.string.invalid_name)
+ }
+ })
+ }
+ }
+ }
+
+ private fun createFolder(path: String, alertDialog: AlertDialog) {
+ try {
+ when {
+ activity.needsStupidWritePermissions(path) -> activity.handleSAFDialog(path) {
+ try {
+ val documentFile = activity.getDocumentFile(path.getParentPath())
+ if (documentFile?.createDirectory(path.getFilenameFromPath()) != null) {
+ sendSuccess(alertDialog, path)
+ } else {
+ activity.toast(R.string.unknown_error_occurred)
+ }
+ } catch (e: SecurityException) {
+ activity.showErrorToast(e)
+ }
+ }
+ File(path).mkdirs() -> sendSuccess(alertDialog, path)
+ else -> activity.toast(R.string.unknown_error_occurred)
+ }
+ } catch (e: Exception) {
+ activity.showErrorToast(e)
+ }
+ }
+
+ private fun sendSuccess(alertDialog: AlertDialog, path: String) {
+ callback(path.trimEnd('/'))
+ alertDialog.dismiss()
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/CustomIntervalPickerDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/CustomIntervalPickerDialog.kt
new file mode 100644
index 0000000..4c62dc6
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/CustomIntervalPickerDialog.kt
@@ -0,0 +1,66 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import android.view.ViewGroup
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.DAY_SECONDS
+import com.simplemobiletools.commons.helpers.HOUR_SECONDS
+import com.simplemobiletools.commons.helpers.MINUTE_SECONDS
+import kotlinx.android.synthetic.main.dialog_custom_interval_picker.view.*
+
+class CustomIntervalPickerDialog(val activity: Activity, val selectedSeconds: Int = 0, val showSeconds: Boolean = false, val callback: (minutes: Int) -> Unit) {
+ var dialog: AlertDialog
+ var view = (activity.layoutInflater.inflate(R.layout.dialog_custom_interval_picker, null) as ViewGroup)
+
+ init {
+ view.apply {
+ dialog_radio_seconds.beVisibleIf(showSeconds)
+ when {
+ selectedSeconds == 0 -> dialog_radio_view.check(R.id.dialog_radio_minutes)
+ selectedSeconds % DAY_SECONDS == 0 -> {
+ dialog_radio_view.check(R.id.dialog_radio_days)
+ dialog_custom_interval_value.setText((selectedSeconds / DAY_SECONDS).toString())
+ }
+ selectedSeconds % HOUR_SECONDS == 0 -> {
+ dialog_radio_view.check(R.id.dialog_radio_hours)
+ dialog_custom_interval_value.setText((selectedSeconds / HOUR_SECONDS).toString())
+ }
+ selectedSeconds % MINUTE_SECONDS == 0 -> {
+ dialog_radio_view.check(R.id.dialog_radio_minutes)
+ dialog_custom_interval_value.setText((selectedSeconds / MINUTE_SECONDS).toString())
+ }
+ else -> {
+ dialog_radio_view.check(R.id.dialog_radio_seconds)
+ dialog_custom_interval_value.setText(selectedSeconds.toString())
+ }
+ }
+ }
+
+ dialog = AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, { dialogInterface, i -> confirmReminder() })
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this) {
+ showKeyboard(view.dialog_custom_interval_value)
+ }
+ }
+ }
+
+ private fun confirmReminder() {
+ val value = view.dialog_custom_interval_value.value
+ val multiplier = getMultiplier(view.dialog_radio_view.checkedRadioButtonId)
+ val minutes = Integer.valueOf(if (value.isEmpty()) "0" else value)
+ callback(minutes * multiplier)
+ activity.hideKeyboard()
+ dialog.dismiss()
+ }
+
+ private fun getMultiplier(id: Int) = when (id) {
+ R.id.dialog_radio_days -> DAY_SECONDS
+ R.id.dialog_radio_hours -> HOUR_SECONDS
+ R.id.dialog_radio_minutes -> MINUTE_SECONDS
+ else -> 1
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/DonateDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/DonateDialog.kt
new file mode 100644
index 0000000..f3be6e7
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/DonateDialog.kt
@@ -0,0 +1,26 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import android.text.Html
+import android.text.method.LinkMovementMethod
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.launchViewIntent
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import kotlinx.android.synthetic.main.dialog_textview.view.*
+
+class DonateDialog(val activity: Activity) {
+ init {
+ val view = activity.layoutInflater.inflate(R.layout.dialog_textview, null).apply {
+ text_view.text = Html.fromHtml(activity.getString(R.string.donate_please))
+ text_view.movementMethod = LinkMovementMethod.getInstance()
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.purchase, { dialog, which -> activity.launchViewIntent(R.string.thank_you_url) })
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this)
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FileConflictDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FileConflictDialog.kt
new file mode 100644
index 0000000..1618ade
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FileConflictDialog.kt
@@ -0,0 +1,58 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.R.id.conflict_dialog_radio_merge
+import com.simplemobiletools.commons.R.id.conflict_dialog_radio_skip
+import com.simplemobiletools.commons.extensions.baseConfig
+import com.simplemobiletools.commons.extensions.beVisibleIf
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import com.simplemobiletools.commons.helpers.CONFLICT_MERGE
+import com.simplemobiletools.commons.helpers.CONFLICT_OVERWRITE
+import com.simplemobiletools.commons.helpers.CONFLICT_SKIP
+import com.simplemobiletools.commons.models.FileDirItem
+import kotlinx.android.synthetic.main.dialog_file_conflict.view.*
+
+class FileConflictDialog(val activity: Activity, val fileDirItem: FileDirItem, val callback: (resolution: Int, applyForAll: Boolean) -> Unit) {
+ val view = activity.layoutInflater.inflate(R.layout.dialog_file_conflict, null)!!
+
+ init {
+ view.apply {
+ val stringBase = if (fileDirItem.isDirectory) R.string.folder_already_exists else R.string.file_already_exists
+ conflict_dialog_title.text = String.format(activity.getString(stringBase), fileDirItem.name)
+ conflict_dialog_apply_to_all.isChecked = activity.baseConfig.lastConflictApplyToAll
+ conflict_dialog_radio_merge.beVisibleIf(fileDirItem.isDirectory)
+
+ val resolutionButton = when (activity.baseConfig.lastConflictResolution) {
+ CONFLICT_OVERWRITE -> conflict_dialog_radio_overwrite
+ CONFLICT_MERGE -> conflict_dialog_radio_merge
+ else -> conflict_dialog_radio_skip
+ }
+ resolutionButton.isChecked = true
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, { dialog, which -> dialogConfirmed() })
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this)
+ }
+ }
+
+ private fun dialogConfirmed() {
+ val resolution = when (view.conflict_dialog_radio_group.checkedRadioButtonId) {
+ conflict_dialog_radio_skip -> CONFLICT_SKIP
+ conflict_dialog_radio_merge -> CONFLICT_MERGE
+ else -> CONFLICT_OVERWRITE
+ }
+
+ val applyToAll = view.conflict_dialog_apply_to_all.isChecked
+ activity.baseConfig.apply {
+ lastConflictApplyToAll = applyToAll
+ lastConflictResolution = resolution
+ }
+
+ callback(resolution, applyToAll)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FilePickerDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FilePickerDialog.kt
new file mode 100644
index 0000000..0b29245
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/FilePickerDialog.kt
@@ -0,0 +1,227 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.os.Environment
+import android.os.Parcelable
+import android.support.v7.app.AlertDialog
+import android.support.v7.widget.LinearLayoutManager
+import android.view.KeyEvent
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.adapters.FilepickerItemsAdapter
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.OTG_PATH
+import com.simplemobiletools.commons.helpers.SORT_BY_SIZE
+import com.simplemobiletools.commons.models.FileDirItem
+import com.simplemobiletools.commons.views.Breadcrumbs
+import kotlinx.android.synthetic.main.dialog_filepicker.view.*
+import java.io.File
+import java.util.*
+
+/**
+ * The only filepicker constructor with a couple optional parameters
+ *
+ * @param activity has to be activity to avoid some Theme.AppCompat issues
+ * @param currPath initial path of the dialog, defaults to the external storage
+ * @param pickFile toggle used to determine if we are picking a file or a folder
+ * @param showHidden toggle for showing hidden items, whose name starts with a dot
+ * @param showFAB toggle the displaying of a Floating Action Button for creating new folders
+ * @param callback the callback used for returning the selected file/folder
+ */
+class FilePickerDialog(val activity: BaseSimpleActivity,
+ var currPath: String = Environment.getExternalStorageDirectory().toString(),
+ val pickFile: Boolean = true,
+ val showHidden: Boolean = false,
+ val showFAB: Boolean = false,
+ val callback: (pickedPath: String) -> Unit) : Breadcrumbs.BreadcrumbsListener {
+
+ private var mFirstUpdate = true
+ private var mPrevPath = ""
+ private var mScrollStates = HashMap()
+
+ private lateinit var mDialog: AlertDialog
+ private var mDialogView = activity.layoutInflater.inflate(R.layout.dialog_filepicker, null)
+
+ init {
+ if (!activity.getDoesFilePathExist(currPath)) {
+ currPath = activity.internalStoragePath
+ }
+
+ if (!activity.getIsPathDirectory(currPath)) {
+ currPath = currPath.getParentPath()
+ }
+
+ // do not allow copying files in the recycle bin manually
+ if (currPath.startsWith(activity.filesDir.absolutePath)) {
+ currPath = activity.internalStoragePath
+ }
+
+ mDialogView.filepicker_breadcrumbs.listener = this
+ tryUpdateItems()
+
+ val builder = AlertDialog.Builder(activity)
+ .setNegativeButton(R.string.cancel, null)
+ .setOnKeyListener { dialogInterface, i, keyEvent ->
+ if (keyEvent.action == KeyEvent.ACTION_UP && i == KeyEvent.KEYCODE_BACK) {
+ val breadcrumbs = mDialogView.filepicker_breadcrumbs
+ if (breadcrumbs.childCount > 1) {
+ breadcrumbs.removeBreadcrumb()
+ currPath = breadcrumbs.getLastItem().path
+ tryUpdateItems()
+ } else {
+ mDialog.dismiss()
+ }
+ }
+ true
+ }
+
+ if (!pickFile)
+ builder.setPositiveButton(R.string.ok, null)
+
+ if (showFAB) {
+ /* mDialogView.filepicker_fab.apply {
+ // beVisible()
+ // setOnClickListener { createNewFolder() }
+ }*/
+ }
+
+ mDialog = builder.create().apply {
+ activity.setupDialogStuff(mDialogView, this, getTitle())
+ }
+
+ if (!pickFile) {
+ mDialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener {
+ verifyPath()
+ }
+ }
+ }
+
+ private fun getTitle() = if (pickFile) R.string.select_file else R.string.select_folder
+
+ private fun createNewFolder() {
+ CreateNewFolderDialog(activity, currPath) {
+ callback(it)
+ mDialog.dismiss()
+ }
+ }
+
+ private fun tryUpdateItems() {
+ Thread {
+ getItems(currPath, activity.baseConfig.sorting and SORT_BY_SIZE != 0) {
+ activity.runOnUiThread {
+ updateItems(it)
+ }
+ }
+ }.start()
+ }
+
+ private fun updateItems(items: List) {
+ if (!containsDirectory(items) && !mFirstUpdate && !pickFile && !showFAB) {
+ verifyPath()
+ return
+ }
+
+ val sortedItems = items.sortedWith(compareBy({ !it.isDirectory }, { it.name.toLowerCase() }))
+
+ val adapter = FilepickerItemsAdapter(activity, sortedItems, mDialogView.filepicker_list) {
+ if ((it as FileDirItem).isDirectory) {
+ currPath = it.path
+ tryUpdateItems()
+ } else if (pickFile) {
+ currPath = it.path
+ verifyPath()
+ }
+ }
+ // adapter.addVerticalDividers(true)
+
+ val layoutManager = mDialogView.filepicker_list.layoutManager as LinearLayoutManager
+ mScrollStates[mPrevPath.trimEnd('/')] = layoutManager.onSaveInstanceState()
+
+ mDialogView.apply {
+ filepicker_list.adapter = adapter
+ filepicker_breadcrumbs.setBreadcrumb(currPath)
+ filepicker_fastscroller.allowBubbleDisplay = context.baseConfig.showInfoBubble
+ filepicker_fastscroller.setViews(filepicker_list) {
+ filepicker_fastscroller.updateBubbleText(sortedItems.getOrNull(it)?.getBubbleText() ?: "")
+ }
+
+ layoutManager.onRestoreInstanceState(mScrollStates[currPath.trimEnd('/')])
+ filepicker_list.onGlobalLayout {
+ filepicker_fastscroller.setScrollToY(filepicker_list.computeVerticalScrollOffset())
+ }
+ }
+
+ mFirstUpdate = false
+ mPrevPath = currPath
+ }
+
+ private fun verifyPath() {
+ if (currPath.startsWith(OTG_PATH)) {
+ val fileDocument = activity.getSomeDocumentFile(currPath) ?: return
+ if ((pickFile && fileDocument.isFile) || (!pickFile && fileDocument.isDirectory)) {
+ sendSuccess()
+ }
+ } else {
+ val file = File(currPath)
+ if ((pickFile && file.isFile) || (!pickFile && file.isDirectory)) {
+ sendSuccess()
+ }
+ }
+ }
+
+ private fun sendSuccess() {
+ currPath = if (currPath == OTG_PATH || currPath.length == 1) {
+ currPath
+ } else {
+ currPath.trimEnd('/')
+ }
+ callback(currPath)
+ mDialog.dismiss()
+ }
+
+ private fun getItems(path: String, getProperFileSize: Boolean, callback: (List) -> Unit) {
+ if (path.startsWith(OTG_PATH)) {
+ activity.getOTGItems(path, showHidden, getProperFileSize, callback)
+ } else {
+ getRegularItems(path, getProperFileSize, callback)
+ }
+ }
+
+ private fun getRegularItems(path: String, getProperFileSize: Boolean, callback: (List) -> Unit) {
+ val items = ArrayList()
+ val base = File(path)
+ val files = base.listFiles()
+ if (files == null) {
+ callback(items)
+ return
+ }
+
+ for (file in files) {
+ if (!showHidden && file.isHidden) {
+ continue
+ }
+
+ val curPath = file.absolutePath
+ val curName = curPath.getFilenameFromPath()
+ val size = if (getProperFileSize) file.getProperSize(showHidden) else file.length()
+ items.add(FileDirItem(curPath, curName, file.isDirectory, file.getDirectChildrenCount(showHidden), size))
+ }
+ callback(items)
+ }
+
+ private fun containsDirectory(items: List) = items.any { it.isDirectory }
+
+ override fun breadcrumbClicked(id: Int) {
+ if (id == 0) {
+ StoragePickerDialog(activity, currPath) {
+ currPath = it
+ tryUpdateItems()
+ }
+ } else {
+ val item = mDialogView.filepicker_breadcrumbs.getChildAt(id).tag as FileDirItem
+ if (currPath != item.path.trimEnd('/')) {
+ currPath = item.path
+ tryUpdateItems()
+ }
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/LineColorPickerDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/LineColorPickerDialog.kt
new file mode 100644
index 0000000..97798cd
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/LineColorPickerDialog.kt
@@ -0,0 +1,142 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.support.v7.app.AlertDialog
+import android.view.View
+import android.view.WindowManager
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.interfaces.LineColorPickerListener
+import kotlinx.android.synthetic.main.dialog_line_color_picker.view.*
+import java.util.*
+
+class LineColorPickerDialog(val activity: BaseSimpleActivity, val color: Int, val isPrimaryColorPicker: Boolean, val primaryColors: Int = R.array.md_primary_colors,
+ val appIconIDs: ArrayList? = null, val callback: (wasPositivePressed: Boolean, color: Int) -> Unit) {
+
+ private val PRIMARY_COLORS_COUNT = 19
+ private val DEFAULT_PRIMARY_COLOR_INDEX = 14
+ private val DEFAULT_SECONDARY_COLOR_INDEX = 6
+ private val DEFAULT_COLOR_VALUE = activity.resources.getColor(R.color.color_primary)
+
+ private var wasDimmedBackgroundRemoved = false
+ private var dialog: AlertDialog? = null
+ private var view: View = activity.layoutInflater.inflate(R.layout.dialog_line_color_picker, null)
+
+ init {
+ view.apply {
+ hex_code.text = color.toHex()
+ hex_code.setOnLongClickListener {
+ activity.copyToClipboard(hex_code.value.substring(1))
+ true
+ }
+
+ line_color_picker_icon.beGoneIf(isPrimaryColorPicker)
+ val indexes = getColorIndexes(color)
+
+ val primaryColorIndex = indexes.first
+ primaryColorChanged(primaryColorIndex)
+ primary_line_color_picker.updateColors(getColors(primaryColors), primaryColorIndex)
+ primary_line_color_picker.listener = object : LineColorPickerListener {
+ override fun colorChanged(index: Int, color: Int) {
+ val secondaryColors = getColorsForIndex(index)
+ secondary_line_color_picker.updateColors(secondaryColors)
+
+ val newColor = if (isPrimaryColorPicker) secondary_line_color_picker.getCurrentColor() else color
+ colorUpdated(newColor)
+
+ if (!isPrimaryColorPicker) {
+ primaryColorChanged(index)
+ }
+ }
+ }
+
+ secondary_line_color_picker.beVisibleIf(isPrimaryColorPicker)
+ secondary_line_color_picker.updateColors(getColorsForIndex(primaryColorIndex), indexes.second)
+ secondary_line_color_picker.listener = object : LineColorPickerListener {
+ override fun colorChanged(index: Int, color: Int) {
+ colorUpdated(color)
+ }
+ }
+ }
+
+ dialog = AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
+ .setNegativeButton(R.string.cancel) { dialog, which -> dialogDismissed() }
+ .setOnCancelListener { dialogDismissed() }
+ .create().apply {
+ activity.setupDialogStuff(view, this)
+ }
+ }
+
+ fun getSpecificColor() = view.secondary_line_color_picker.getCurrentColor()
+
+ private fun colorUpdated(color: Int) {
+ view.hex_code.text = color.toHex()
+ if (isPrimaryColorPicker) {
+ activity.updateActionbarColor(color)
+ activity.setTheme(activity.getThemeId(color))
+
+ if (!wasDimmedBackgroundRemoved) {
+ dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+ wasDimmedBackgroundRemoved = true
+ }
+ }
+ }
+
+ private fun getColorIndexes(color: Int): Pair {
+ if (color == DEFAULT_COLOR_VALUE) {
+ return getDefaultColorPair()
+ }
+
+ for (i in 0 until PRIMARY_COLORS_COUNT) {
+ getColorsForIndex(i).indexOfFirst { color == it }.apply {
+ if (this != -1) {
+ return Pair(i, this)
+ }
+ }
+ }
+
+ return getDefaultColorPair()
+ }
+
+ private fun primaryColorChanged(index: Int) {
+ view.line_color_picker_icon.setImageResource(appIconIDs?.getOrNull(index) ?: 0)
+ }
+
+ private fun getDefaultColorPair() = Pair(DEFAULT_PRIMARY_COLOR_INDEX, DEFAULT_SECONDARY_COLOR_INDEX)
+
+ private fun dialogDismissed() {
+ callback(false, 0)
+ }
+
+ private fun dialogConfirmed() {
+ val targetView = if (isPrimaryColorPicker) view.secondary_line_color_picker else view.primary_line_color_picker
+ val color = targetView.getCurrentColor()
+ callback(true, color)
+ }
+
+ private fun getColorsForIndex(index: Int) = when (index) {
+ 0 -> getColors(R.array.md_reds)
+ 1 -> getColors(R.array.md_pinks)
+ 2 -> getColors(R.array.md_purples)
+ 3 -> getColors(R.array.md_deep_purples)
+ 4 -> getColors(R.array.md_indigos)
+ 5 -> getColors(R.array.md_blues)
+ 6 -> getColors(R.array.md_light_blues)
+ 7 -> getColors(R.array.md_cyans)
+ 8 -> getColors(R.array.md_teals)
+ 9 -> getColors(R.array.md_greens)
+ 10 -> getColors(R.array.md_light_greens)
+ 11 -> getColors(R.array.md_limes)
+ 12 -> getColors(R.array.md_yellows)
+ 13 -> getColors(R.array.md_ambers)
+ 14 -> getColors(R.array.md_oranges)
+ 15 -> getColors(R.array.md_deep_oranges)
+ 16 -> getColors(R.array.md_browns)
+ 17 -> getColors(R.array.md_blue_greys)
+ 18 -> getColors(R.array.md_greys)
+ else -> throw RuntimeException("Invalid color id $index")
+ }
+
+ private fun getColors(id: Int) = activity.resources.getIntArray(id).toCollection(ArrayList())
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/NewAppDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/NewAppDialog.kt
new file mode 100644
index 0000000..1bdf8b3
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/NewAppDialog.kt
@@ -0,0 +1,25 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import android.text.Html
+import android.text.method.LinkMovementMethod
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import kotlinx.android.synthetic.main.dialog_textview.view.*
+
+class NewAppDialog(val activity: Activity, val packageName: String, val title: String) {
+ init {
+ val view = activity.layoutInflater.inflate(R.layout.dialog_textview, null).apply {
+ val text = String.format(activity.getString(R.string.new_app), "https://play.google.com/store/apps/details?id=$packageName", title)
+ text_view.text = Html.fromHtml(text)
+ text_view.movementMethod = LinkMovementMethod.getInstance()
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this)
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/PropertiesDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/PropertiesDialog.kt
new file mode 100644
index 0000000..e1b6333
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/PropertiesDialog.kt
@@ -0,0 +1,215 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.content.res.Resources
+import android.media.ExifInterface
+import android.provider.MediaStore
+import android.support.v7.app.AlertDialog
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.sumByInt
+import com.simplemobiletools.commons.helpers.sumByLong
+import com.simplemobiletools.commons.models.FileDirItem
+import kotlinx.android.synthetic.main.dialog_properties.view.*
+import kotlinx.android.synthetic.main.property_item.view.*
+import java.io.FileNotFoundException
+import java.util.*
+
+class PropertiesDialog() {
+ private lateinit var mInflater: LayoutInflater
+ private lateinit var mPropertyView: ViewGroup
+ private lateinit var mResources: Resources
+
+ /**
+ * A File Properties dialog constructor with an optional parameter, usable at 1 file selected
+ *
+ * @param activity request activity to avoid some Theme.AppCompat issues
+ * @param path the file path
+ * @param countHiddenItems toggle determining if we will count hidden files themselves and their sizes (reasonable only at directory properties)
+ */
+ constructor(activity: Activity, path: String, countHiddenItems: Boolean = false) : this() {
+ if (!activity.getDoesFilePathExist(path)) {
+ activity.toast(String.format(activity.getString(R.string.source_file_doesnt_exist), path))
+ return
+ }
+
+ mInflater = LayoutInflater.from(activity)
+ mResources = activity.resources
+ val view = mInflater.inflate(R.layout.dialog_properties, null)
+ mPropertyView = view.properties_holder
+
+ val fileDirItem = FileDirItem(path, path.getFilenameFromPath(), activity.getIsPathDirectory(path))
+ addProperty(R.string.name, fileDirItem.name)
+ addProperty(R.string.path, fileDirItem.getParentPath())
+ addProperty(R.string.size, "…", R.id.properties_size)
+
+ Thread {
+ val fileCount = fileDirItem.getProperFileCount(activity, countHiddenItems)
+ val size = fileDirItem.getProperSize(activity, countHiddenItems).formatSize()
+ activity.runOnUiThread {
+ view.findViewById(R.id.properties_size).property_value.text = size
+
+ if (fileDirItem.isDirectory) {
+ view.findViewById(R.id.properties_file_count).property_value.text = fileCount.toString()
+ }
+ }
+
+ if (!fileDirItem.isDirectory) {
+ val projection = arrayOf(MediaStore.Images.Media.DATE_MODIFIED)
+ val uri = MediaStore.Files.getContentUri("external")
+ val selection = "${MediaStore.MediaColumns.DATA} = ?"
+ val selectionArgs = arrayOf(path)
+ val cursor = activity.contentResolver.query(uri, projection, selection, selectionArgs, null)
+ cursor?.use {
+ if (cursor.moveToFirst()) {
+ val dateModified = cursor.getLongValue(MediaStore.Images.Media.DATE_MODIFIED) * 1000L
+ updateLastModified(activity, view, dateModified)
+ } else {
+ updateLastModified(activity, view, fileDirItem.getLastModified(activity))
+ }
+ }
+ }
+ }.start()
+
+ when {
+ fileDirItem.isDirectory -> {
+ addProperty(R.string.direct_children_count, fileDirItem.getDirectChildrenCount(activity, countHiddenItems).toString())
+ addProperty(R.string.files_count, "…", R.id.properties_file_count)
+ }
+ fileDirItem.path.isImageSlow() -> {
+ fileDirItem.getResolution()?.let { addProperty(R.string.resolution, it.formatAsResolution()) }
+ }
+ fileDirItem.path.isAudioSlow() -> {
+ fileDirItem.getDuration()?.let { addProperty(R.string.duration, it) }
+ fileDirItem.getSongTitle()?.let { addProperty(R.string.song_title, it) }
+ fileDirItem.getArtist()?.let { addProperty(R.string.artist, it) }
+ fileDirItem.getAlbum()?.let { addProperty(R.string.album, it) }
+ }
+ fileDirItem.path.isVideoSlow() -> {
+ fileDirItem.getDuration()?.let { addProperty(R.string.duration, it) }
+ fileDirItem.getResolution()?.let { addProperty(R.string.resolution, it.formatAsResolution()) }
+ fileDirItem.getArtist()?.let { addProperty(R.string.artist, it) }
+ fileDirItem.getAlbum()?.let { addProperty(R.string.album, it) }
+ }
+ }
+
+ if (fileDirItem.isDirectory) {
+ addProperty(R.string.last_modified, fileDirItem.getLastModified(activity).formatDate())
+ } else {
+ addProperty(R.string.last_modified, "…", R.id.properties_last_modified)
+ try {
+ addExifProperties(path)
+ } catch (e: FileNotFoundException) {
+ activity.toast(R.string.unknown_error_occurred)
+ return
+ }
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this, R.string.properties)
+ }
+ }
+
+ private fun updateLastModified(activity: Activity, view: View, timestamp: Long) {
+ activity.runOnUiThread {
+ view.findViewById(R.id.properties_last_modified).property_value.text = timestamp.formatDate()
+ }
+ }
+
+ /**
+ * A File Properties dialog constructor with an optional parameter, usable at multiple items selected
+ *
+ * @param activity request activity to avoid some Theme.AppCompat issues
+ * @param path the file path
+ * @param countHiddenItems toggle determining if we will count hidden files themselves and their sizes
+ */
+ constructor(activity: Activity, paths: List, countHiddenItems: Boolean = false) : this() {
+ mInflater = LayoutInflater.from(activity)
+ mResources = activity.resources
+ val view = mInflater.inflate(R.layout.dialog_properties, null)
+ mPropertyView = view.properties_holder
+
+ val fileDirItems = ArrayList(paths.size)
+ paths.forEach {
+ val fileDirItem = FileDirItem(it, it.getFilenameFromPath(), activity.getIsPathDirectory(it))
+ fileDirItems.add(fileDirItem)
+ }
+
+ val isSameParent = isSameParent(fileDirItems)
+
+ addProperty(R.string.items_selected, paths.size.toString())
+ if (isSameParent) {
+ addProperty(R.string.path, fileDirItems[0].getParentPath())
+ }
+
+ addProperty(R.string.size, "…", R.id.properties_size)
+ addProperty(R.string.files_count, "…", R.id.properties_file_count)
+
+ Thread {
+ val fileCount = fileDirItems.sumByInt { it.getProperFileCount(activity, countHiddenItems) }
+ val size = fileDirItems.sumByLong { it.getProperSize(activity, countHiddenItems) }.formatSize()
+ activity.runOnUiThread {
+ view.findViewById(R.id.properties_size).property_value.text = size
+ view.findViewById(R.id.properties_file_count).property_value.text = fileCount.toString()
+ }
+ }.start()
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this, R.string.properties)
+ }
+ }
+
+ private fun addExifProperties(path: String) {
+ val exif = ExifInterface(path)
+ val dateTaken = path.getExifDateTaken(exif)
+ if (dateTaken.isNotEmpty()) {
+ addProperty(R.string.date_taken, dateTaken)
+ }
+
+ val cameraModel = path.getExifCameraModel(exif)
+ if (cameraModel.isNotEmpty()) {
+ addProperty(R.string.camera, cameraModel)
+ }
+
+ val exifString = path.getExifProperties(exif)
+ if (exifString.isNotEmpty()) {
+ addProperty(R.string.exif, exifString)
+ }
+ }
+
+ private fun isSameParent(fileDirItems: List): Boolean {
+ var parent = fileDirItems[0].getParentPath()
+ for (file in fileDirItems) {
+ val curParent = file.getParentPath()
+ if (curParent != parent) {
+ return false
+ }
+
+ parent = curParent
+ }
+ return true
+ }
+
+ private fun addProperty(labelId: Int, value: String?, viewId: Int = 0) {
+ if (value == null)
+ return
+
+ mInflater.inflate(R.layout.property_item, mPropertyView, false).apply {
+ property_label.text = mResources.getString(labelId)
+ property_value.text = value
+ mPropertyView.properties_holder.addView(this)
+
+ if (viewId != 0) {
+ id = viewId
+ }
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/PurchaseThankYouDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/PurchaseThankYouDialog.kt
new file mode 100644
index 0000000..7fba0a3
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/PurchaseThankYouDialog.kt
@@ -0,0 +1,26 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import android.text.Html
+import android.text.method.LinkMovementMethod
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.launchPurchaseThankYouIntent
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import kotlinx.android.synthetic.main.dialog_purchase_thank_you.view.*
+
+class PurchaseThankYouDialog(val activity: Activity) {
+ init {
+ val view = activity.layoutInflater.inflate(R.layout.dialog_purchase_thank_you, null).apply {
+ purchase_thank_you.text = Html.fromHtml(activity.getString(R.string.purchase_thank_you))
+ purchase_thank_you.movementMethod = LinkMovementMethod.getInstance()
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.purchase) { dialog, which -> activity.launchPurchaseThankYouIntent() }
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this)
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/RadioGroupDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/RadioGroupDialog.kt
new file mode 100644
index 0000000..8dd0275
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/RadioGroupDialog.kt
@@ -0,0 +1,69 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RadioButton
+import android.widget.RadioGroup
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.onGlobalLayout
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import com.simplemobiletools.commons.models.RadioItem
+import kotlinx.android.synthetic.main.dialog_radio_group.view.*
+import java.util.*
+
+class RadioGroupDialog(val activity: Activity, val items: ArrayList, val checkedItemId: Int = -1, val titleId: Int = 0,
+ showOKButton: Boolean = false, val cancelCallback: (() -> Unit)? = null, val callback: (newValue: Any) -> Unit) {
+ private val dialog: AlertDialog
+ private var wasInit = false
+ private var selectedItemId = -1
+
+ init {
+ val view = activity.layoutInflater.inflate(R.layout.dialog_radio_group, null)
+ view.dialog_radio_group.apply {
+ for (i in 0 until items.size) {
+ val radioButton = (activity.layoutInflater.inflate(R.layout.radio_button, null) as RadioButton).apply {
+ text = items[i].title
+ isChecked = items[i].id == checkedItemId
+ id = i
+ setOnClickListener { itemSelected(i) }
+ }
+
+ if (items[i].id == checkedItemId) {
+ selectedItemId = i
+ }
+
+ addView(radioButton, RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
+ }
+ }
+
+ val builder = AlertDialog.Builder(activity)
+ .setOnCancelListener { cancelCallback?.invoke() }
+
+ if (selectedItemId != -1 && showOKButton) {
+ builder.setPositiveButton(R.string.ok, { dialog, which -> itemSelected(selectedItemId) })
+ }
+
+ dialog = builder.create().apply {
+ activity.setupDialogStuff(view, this, titleId)
+ }
+
+ if (selectedItemId != -1) {
+ view.dialog_radio_holder.apply {
+ onGlobalLayout {
+ scrollY = view.dialog_radio_group.findViewById(selectedItemId).bottom - height
+ }
+ }
+ }
+
+ wasInit = true
+ }
+
+ private fun itemSelected(checkedId: Int) {
+ if (wasInit) {
+ callback(items[checkedId].value)
+ dialog.dismiss()
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/RenameItemDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/RenameItemDialog.kt
new file mode 100644
index 0000000..83c7239
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/RenameItemDialog.kt
@@ -0,0 +1,80 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.support.v7.app.AlertDialog
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.*
+import kotlinx.android.synthetic.main.dialog_rename_item.view.*
+import java.util.*
+
+class RenameItemDialog(val activity: BaseSimpleActivity, val path: String, val callback: (newPath: String) -> Unit) {
+ init {
+ val fullName = path.getFilenameFromPath()
+ val dotAt = fullName.lastIndexOf(".")
+ var name = fullName
+
+ val view = activity.layoutInflater.inflate(R.layout.dialog_rename_item, null).apply {
+ if (dotAt > 0 && !activity.getIsPathDirectory(path)) {
+ name = fullName.substring(0, dotAt)
+ val extension = fullName.substring(dotAt + 1)
+ rename_item_extension.setText(extension)
+ } else {
+ rename_item_extension_label.beGone()
+ rename_item_extension.beGone()
+ }
+
+ rename_item_name.setText(name)
+ rename_item_path.text = activity.humanizePath(path.getParentPath())
+ }
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, null)
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this, R.string.rename) {
+ showKeyboard(view.rename_item_name)
+ getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
+ var newName = view.rename_item_name.value
+ val newExtension = view.rename_item_extension.value
+
+ if (newName.isEmpty()) {
+ activity.toast(R.string.empty_name)
+ return@setOnClickListener
+ }
+
+ if (!newName.isAValidFilename()) {
+ activity.toast(R.string.invalid_name)
+ return@setOnClickListener
+ }
+
+ val updatedPaths = ArrayList()
+ updatedPaths.add(path)
+ if (!newExtension.isEmpty()) {
+ newName += ".$newExtension"
+ }
+
+ if (!activity.getDoesFilePathExist(path)) {
+ activity.toast(String.format(activity.getString(R.string.source_file_doesnt_exist), path))
+ return@setOnClickListener
+ }
+
+ val newPath = "${path.getParentPath()}/$newName"
+ if (activity.getDoesFilePathExist(newPath)) {
+ activity.toast(R.string.name_taken)
+ return@setOnClickListener
+ }
+
+ updatedPaths.add(newPath)
+ activity.renameFile(path, newPath) {
+ if (it) {
+ callback(newPath)
+ dismiss()
+ } else {
+ activity.toast(R.string.unknown_error_occurred)
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/SelectAlarmSoundDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/SelectAlarmSoundDialog.kt
new file mode 100644
index 0000000..fe4ceae
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/SelectAlarmSoundDialog.kt
@@ -0,0 +1,164 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.annotation.TargetApi
+import android.content.Intent
+import android.media.MediaPlayer
+import android.net.Uri
+import android.os.Build
+import android.support.v7.app.AlertDialog
+import android.view.ViewGroup
+import android.widget.RadioGroup
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.SILENT
+import com.simplemobiletools.commons.helpers.isKitkatPlus
+import com.simplemobiletools.commons.models.AlarmSound
+import com.simplemobiletools.commons.models.RadioItem
+import com.simplemobiletools.commons.views.MyCompatRadioButton
+import kotlinx.android.synthetic.main.dialog_select_alarm_sound.view.*
+import java.util.*
+
+class SelectAlarmSoundDialog(val activity: BaseSimpleActivity, val currentUri: String, val audioStream: Int, val pickAudioIntentId: Int,
+ val type: Int, val loopAudio: Boolean, val onAlarmPicked: (alarmSound: AlarmSound?) -> Unit,
+ val onAlarmSoundDeleted: (alarmSound: AlarmSound) -> Unit) {
+ private val ADD_NEW_SOUND_ID = -2
+
+ private val view = activity.layoutInflater.inflate(R.layout.dialog_select_alarm_sound, null)
+ private var systemAlarmSounds = ArrayList()
+ private var yourAlarmSounds = ArrayList()
+ private var mediaPlayer: MediaPlayer? = null
+ private val config = activity.baseConfig
+ private val dialog: AlertDialog
+
+ init {
+ activity.getAlarmSounds(type) {
+ systemAlarmSounds = it
+ gotSystemAlarms()
+ }
+
+ view.dialog_select_alarm_your_label.setTextColor(activity.getAdjustedPrimaryColor())
+ view.dialog_select_alarm_system_label.setTextColor(activity.getAdjustedPrimaryColor())
+
+ addYourAlarms()
+
+ dialog = AlertDialog.Builder(activity)
+ .setOnDismissListener { mediaPlayer?.stop() }
+ .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
+ .setNegativeButton(R.string.cancel, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this)
+ window.volumeControlStream = audioStream
+ }
+ }
+
+ private fun addYourAlarms() {
+ view.dialog_select_alarm_your_radio.removeAllViews()
+ val token = object : TypeToken>() {}.type
+ yourAlarmSounds = Gson().fromJson>(config.yourAlarmSounds, token) ?: ArrayList()
+ yourAlarmSounds.add(AlarmSound(ADD_NEW_SOUND_ID, activity.getString(R.string.add_new_sound), ""))
+ yourAlarmSounds.forEach {
+ addAlarmSound(it, view.dialog_select_alarm_your_radio)
+ }
+ }
+
+ private fun gotSystemAlarms() {
+ systemAlarmSounds.forEach {
+ addAlarmSound(it, view.dialog_select_alarm_system_radio)
+ }
+ }
+
+ private fun addAlarmSound(alarmSound: AlarmSound, holder: ViewGroup) {
+ val radioButton = (activity.layoutInflater.inflate(R.layout.item_select_alarm_sound, null) as MyCompatRadioButton).apply {
+ text = alarmSound.title
+ isChecked = alarmSound.uri == currentUri
+ id = alarmSound.id
+ setColors(config.textColor, activity.getAdjustedPrimaryColor(), config.backgroundColor)
+ setOnClickListener {
+ alarmClicked(alarmSound)
+
+ if (holder == view.dialog_select_alarm_system_radio) {
+ view.dialog_select_alarm_your_radio.clearCheck()
+ } else {
+ view.dialog_select_alarm_system_radio.clearCheck()
+ }
+ }
+
+ if (alarmSound.id != -2 && holder == view.dialog_select_alarm_your_radio) {
+ setOnLongClickListener {
+ val items = arrayListOf(RadioItem(1, context.getString(R.string.remove)))
+
+ RadioGroupDialog(activity, items) {
+ removeAlarmSound(alarmSound)
+ }
+ true
+ }
+ }
+ }
+
+ holder.addView(radioButton, RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT))
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private fun alarmClicked(alarmSound: AlarmSound) {
+ when {
+ alarmSound.uri == SILENT -> mediaPlayer?.stop()
+ alarmSound.id == ADD_NEW_SOUND_ID -> {
+ val action = if (isKitkatPlus()) Intent.ACTION_OPEN_DOCUMENT else Intent.ACTION_GET_CONTENT
+ Intent(action).apply {
+ type = "audio/*"
+ activity.startActivityForResult(this, pickAudioIntentId)
+
+ if (isKitkatPlus()) {
+ flags = flags or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ }
+ }
+ dialog.dismiss()
+ }
+ else -> try {
+ mediaPlayer?.reset()
+ if (mediaPlayer == null) {
+ mediaPlayer = MediaPlayer().apply {
+ setAudioStreamType(audioStream)
+ isLooping = loopAudio
+ }
+ }
+
+ mediaPlayer?.apply {
+ setDataSource(activity, Uri.parse(alarmSound.uri))
+ prepare()
+ start()
+ }
+ } catch (e: Exception) {
+ activity.showErrorToast(e)
+ }
+ }
+ }
+
+ private fun removeAlarmSound(alarmSound: AlarmSound) {
+ val token = object : TypeToken>() {}.type
+ yourAlarmSounds = Gson().fromJson>(config.yourAlarmSounds, token) ?: ArrayList()
+ yourAlarmSounds.remove(alarmSound)
+ config.yourAlarmSounds = Gson().toJson(yourAlarmSounds)
+ addYourAlarms()
+
+ if (alarmSound.id == view.dialog_select_alarm_your_radio.checkedRadioButtonId) {
+ view.dialog_select_alarm_your_radio.clearCheck()
+ view.dialog_select_alarm_system_radio.check(systemAlarmSounds.firstOrNull()?.id ?: 0)
+ }
+
+ onAlarmSoundDeleted(alarmSound)
+ }
+
+ private fun dialogConfirmed() {
+ if (view.dialog_select_alarm_your_radio.checkedRadioButtonId != -1) {
+ val checkedId = view.dialog_select_alarm_your_radio.checkedRadioButtonId
+ onAlarmPicked(yourAlarmSounds.firstOrNull { it.id == checkedId })
+ } else {
+ val checkedId = view.dialog_select_alarm_system_radio.checkedRadioButtonId
+ onAlarmPicked(systemAlarmSounds.firstOrNull { it.id == checkedId })
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/StoragePickerDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/StoragePickerDialog.kt
new file mode 100644
index 0000000..7995ef5
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/StoragePickerDialog.kt
@@ -0,0 +1,123 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.support.v7.app.AlertDialog
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import android.widget.RadioButton
+import android.widget.RadioGroup
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.OTG_PATH
+import kotlinx.android.synthetic.main.dialog_radio_group.view.*
+
+/**
+ * A dialog for choosing between internal, root, SD card (optional) storage
+ *
+ * @param activity has to be activity to avoid some Theme.AppCompat issues
+ * @param currPath current path to decide which storage should be preselected
+ * @param callback an anonymous function
+ *
+ */
+class StoragePickerDialog(val activity: BaseSimpleActivity, currPath: String, val callback: (pickedPath: String) -> Unit) {
+ private val ID_INTERNAL = 1
+ private val ID_SD = 2
+ private val ID_OTG = 3
+ private val ID_ROOT = 4
+
+ private var mDialog: AlertDialog
+ private var radioGroup: RadioGroup
+ private var defaultSelectedId = 0
+
+ init {
+ val inflater = LayoutInflater.from(activity)
+ val resources = activity.resources
+ val layoutParams = RadioGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ val view = inflater.inflate(R.layout.dialog_radio_group, null)
+ radioGroup = view.dialog_radio_group
+ val basePath = currPath.getBasePath(activity)
+
+ val internalButton = inflater.inflate(R.layout.radio_button, null) as RadioButton
+ internalButton.apply {
+ id = ID_INTERNAL
+ text = resources.getString(R.string.internal)
+ isChecked = basePath == context.internalStoragePath
+ setOnClickListener { internalPicked() }
+ if (isChecked) {
+ defaultSelectedId = id
+ }
+ }
+ radioGroup.addView(internalButton, layoutParams)
+
+ if (activity.hasExternalSDCard()) {
+ val sdButton = inflater.inflate(R.layout.radio_button, null) as RadioButton
+ sdButton.apply {
+ id = ID_SD
+ text = resources.getString(R.string.sd_card)
+ isChecked = basePath == context.sdCardPath
+ setOnClickListener { sdPicked() }
+ if (isChecked) {
+ defaultSelectedId = id
+ }
+ }
+ radioGroup.addView(sdButton, layoutParams)
+ }
+
+ if (activity.hasOTGConnected()) {
+ val otgButton = inflater.inflate(R.layout.radio_button, null) as RadioButton
+ otgButton.apply {
+ id = ID_OTG
+ text = resources.getString(R.string.otg)
+ isChecked = basePath == OTG_PATH
+ setOnClickListener { otgPicked() }
+ if (isChecked) {
+ defaultSelectedId = id
+ }
+ }
+ radioGroup.addView(otgButton, layoutParams)
+ }
+
+ val rootButton = inflater.inflate(R.layout.radio_button, null) as RadioButton
+ rootButton.apply {
+ id = ID_ROOT
+ text = resources.getString(R.string.root)
+ isChecked = basePath == "/"
+ setOnClickListener { rootPicked() }
+ if (isChecked) {
+ defaultSelectedId = id
+ }
+ }
+ radioGroup.addView(rootButton, layoutParams)
+
+ mDialog = AlertDialog.Builder(activity)
+ .create().apply {
+ activity.setupDialogStuff(view, this, R.string.select_storage)
+ }
+ }
+
+ private fun internalPicked() {
+ mDialog.dismiss()
+ callback(activity.internalStoragePath)
+ }
+
+ private fun sdPicked() {
+ mDialog.dismiss()
+ callback(activity.sdCardPath)
+ }
+
+ private fun otgPicked() {
+ activity.handleOTGPermission {
+ if (it) {
+ callback(OTG_PATH)
+ mDialog.dismiss()
+ } else {
+ radioGroup.check(defaultSelectedId)
+ }
+ }
+ }
+
+ private fun rootPicked() {
+ mDialog.dismiss()
+ callback("/")
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/WhatsNewDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/WhatsNewDialog.kt
new file mode 100644
index 0000000..f4b9ff4
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/WhatsNewDialog.kt
@@ -0,0 +1,35 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import android.view.LayoutInflater
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import com.simplemobiletools.commons.models.Release
+import kotlinx.android.synthetic.main.dialog_whats_new.view.*
+
+class WhatsNewDialog(val activity: Activity, val releases: List) {
+ init {
+ val view = LayoutInflater.from(activity).inflate(R.layout.dialog_whats_new, null)
+ view.whats_new_content.text = getNewReleases()
+
+ AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, null)
+ .create().apply {
+ activity.setupDialogStuff(view, this, R.string.whats_new)
+ }
+ }
+
+ private fun getNewReleases(): String {
+ val sb = StringBuilder()
+
+ releases.forEach {
+ val parts = activity.getString(it.textId).split("\n").map(String::trim)
+ parts.forEach {
+ sb.append("- $it\n")
+ }
+ }
+
+ return sb.toString()
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/WritePermissionDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/WritePermissionDialog.kt
new file mode 100644
index 0000000..dbf2b9d
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/dialogs/WritePermissionDialog.kt
@@ -0,0 +1,41 @@
+package com.simplemobiletools.commons.dialogs
+
+import android.app.Activity
+import android.support.v7.app.AlertDialog
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import kotlinx.android.synthetic.main.dialog_write_permission.view.*
+import kotlinx.android.synthetic.main.dialog_write_permission_otg.view.*
+
+class WritePermissionDialog(activity: Activity, val isOTG: Boolean, val callback: () -> Unit) {
+ var dialog: AlertDialog
+
+ init {
+ val layout = if (isOTG) R.layout.dialog_write_permission_otg else R.layout.dialog_write_permission
+ val view = activity.layoutInflater.inflate(layout, null)
+
+ val glide = Glide.with(activity)
+ val crossFade = DrawableTransitionOptions.withCrossFade()
+ if (isOTG) {
+ glide.load(R.drawable.img_write_storage_otg).transition(crossFade).into(view.write_permissions_dialog_otg_image)
+ } else {
+ glide.load(R.drawable.img_write_storage).transition(crossFade).into(view.write_permissions_dialog_image)
+ glide.load(R.drawable.img_write_storage_sd).transition(crossFade).into(view.write_permissions_dialog_image_sd)
+ }
+
+ dialog = AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok) { dialog, which -> dialogConfirmed() }
+ .setOnCancelListener { BaseSimpleActivity.funAfterSAFPermission = null }
+ .create().apply {
+ activity.setupDialogStuff(view, this, R.string.confirm_storage_access_title)
+ }
+ }
+
+ private fun dialogConfirmed() {
+ dialog.dismiss()
+ callback()
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity-themes.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity-themes.kt
new file mode 100644
index 0000000..ebbe464
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity-themes.kt
@@ -0,0 +1,198 @@
+package com.simplemobiletools.commons.extensions
+
+import android.app.Activity
+import com.simplemobiletools.commons.R
+
+fun Activity.getThemeId(color: Int = baseConfig.primaryColor) = when (color) {
+ -12846 -> R.style.AppTheme_Red_100
+ -1074534 -> R.style.AppTheme_Red_200
+ -1739917 -> R.style.AppTheme_Red_300
+ -1092784 -> R.style.AppTheme_Red_400
+ -769226 -> R.style.AppTheme_Red_500
+ -1754827 -> R.style.AppTheme_Red_600
+ -2937041 -> R.style.AppTheme_Red_700
+ -3790808 -> R.style.AppTheme_Red_800
+ -4776932 -> R.style.AppTheme_Red_900
+
+ -476208 -> R.style.AppTheme_Pink_100
+ -749647 -> R.style.AppTheme_Pink_200
+ -1023342 -> R.style.AppTheme_Pink_300
+ -1294214 -> R.style.AppTheme_Pink_400
+ -1499549 -> R.style.AppTheme_Pink_500
+ -2614432 -> R.style.AppTheme_Pink_600
+ -4056997 -> R.style.AppTheme_Pink_700
+ -5434281 -> R.style.AppTheme_Pink_800
+ -7860657 -> R.style.AppTheme_Pink_900
+
+ -1982745 -> R.style.AppTheme_Purple_100
+ -3238952 -> R.style.AppTheme_Purple_200
+ -4560696 -> R.style.AppTheme_Purple_300
+ -5552196 -> R.style.AppTheme_Purple_400
+ -6543440 -> R.style.AppTheme_Purple_500
+ -7461718 -> R.style.AppTheme_Purple_600
+ -8708190 -> R.style.AppTheme_Purple_700
+ -9823334 -> R.style.AppTheme_Purple_800
+ -11922292 -> R.style.AppTheme_Purple_900
+
+ -3029783 -> R.style.AppTheme_Deep_Purple_100
+ -5005861 -> R.style.AppTheme_Deep_Purple_200
+ -6982195 -> R.style.AppTheme_Deep_Purple_300
+ -8497214 -> R.style.AppTheme_Deep_Purple_400
+ -10011977 -> R.style.AppTheme_Deep_Purple_500
+ -10603087 -> R.style.AppTheme_Deep_Purple_600
+ -11457112 -> R.style.AppTheme_Deep_Purple_700
+ -12245088 -> R.style.AppTheme_Deep_Purple_800
+ -13558894 -> R.style.AppTheme_Deep_Purple_900
+
+ -3814679 -> R.style.AppTheme_Indigo_100
+ -6313766 -> R.style.AppTheme_Indigo_200
+ -8812853 -> R.style.AppTheme_Indigo_300
+ -10720320 -> R.style.AppTheme_Indigo_400
+ -12627531 -> R.style.AppTheme_Indigo_500
+ -13022805 -> R.style.AppTheme_Indigo_600
+ -13615201 -> R.style.AppTheme_Indigo_700
+ -14142061 -> R.style.AppTheme_Indigo_800
+ -15064194 -> R.style.AppTheme_Indigo_900
+
+ -4464901 -> R.style.AppTheme_Blue_100
+ -7288071 -> R.style.AppTheme_Blue_200
+ -10177034 -> R.style.AppTheme_Blue_300
+ -12409355 -> R.style.AppTheme_Blue_400
+ -14575885 -> R.style.AppTheme_Blue_500
+ -14776091 -> R.style.AppTheme_Blue_600
+ -15108398 -> R.style.AppTheme_Blue_700
+ -15374912 -> R.style.AppTheme_Blue_800
+ -15906911 -> R.style.AppTheme_Blue_900
+
+ -4987396 -> R.style.AppTheme_Light_Blue_100
+ -8268550 -> R.style.AppTheme_Light_Blue_200
+ -11549705 -> R.style.AppTheme_Light_Blue_300
+ -14043396 -> R.style.AppTheme_Light_Blue_400
+ -16537100 -> R.style.AppTheme_Light_Blue_500
+ -16540699 -> R.style.AppTheme_Light_Blue_600
+ -16611119 -> R.style.AppTheme_Light_Blue_700
+ -16615491 -> R.style.AppTheme_Light_Blue_800
+ -16689253 -> R.style.AppTheme_Light_Blue_900
+
+ -5051406 -> R.style.AppTheme_Cyan_100
+ -8331542 -> R.style.AppTheme_Cyan_200
+ -11677471 -> R.style.AppTheme_Cyan_300
+ -14235942 -> R.style.AppTheme_Cyan_400
+ -16728876 -> R.style.AppTheme_Cyan_500
+ -16732991 -> R.style.AppTheme_Cyan_600
+ -16738393 -> R.style.AppTheme_Cyan_700
+ -16743537 -> R.style.AppTheme_Cyan_800
+ -16752540 -> R.style.AppTheme_Cyan_900
+
+ -5054501 -> R.style.AppTheme_Teal_100
+ -8336444 -> R.style.AppTheme_Teal_200
+ -11684180 -> R.style.AppTheme_Teal_300
+ -14244198 -> R.style.AppTheme_Teal_400
+ -16738680 -> R.style.AppTheme_Teal_500
+ -16742021 -> R.style.AppTheme_Teal_600
+ -16746133 -> R.style.AppTheme_Teal_700
+ -16750244 -> R.style.AppTheme_Teal_800
+ -16757440 -> R.style.AppTheme_Teal_900
+
+ -3610935 -> R.style.AppTheme_Green_100
+ -5908825 -> R.style.AppTheme_Green_200
+ -8271996 -> R.style.AppTheme_Green_300
+ -10044566 -> R.style.AppTheme_Green_400
+ -11751600 -> R.style.AppTheme_Green_500
+ -12345273 -> R.style.AppTheme_Green_600
+ -13070788 -> R.style.AppTheme_Green_700
+ -13730510 -> R.style.AppTheme_Green_800
+ -14983648 -> R.style.AppTheme_Green_900
+
+ -2298424 -> R.style.AppTheme_Light_Green_100
+ -3808859 -> R.style.AppTheme_Light_Green_200
+ -5319295 -> R.style.AppTheme_Light_Green_300
+ -6501275 -> R.style.AppTheme_Light_Green_400
+ -7617718 -> R.style.AppTheme_Light_Green_500
+ -8604862 -> R.style.AppTheme_Light_Green_600
+ -9920712 -> R.style.AppTheme_Light_Green_700
+ -11171025 -> R.style.AppTheme_Light_Green_800
+ -13407970 -> R.style.AppTheme_Light_Green_900
+
+ -985917 -> R.style.AppTheme_Lime_100
+ -1642852 -> R.style.AppTheme_Lime_200
+ -2300043 -> R.style.AppTheme_Lime_300
+ -2825897 -> R.style.AppTheme_Lime_400
+ -3285959 -> R.style.AppTheme_Lime_500
+ -4142541 -> R.style.AppTheme_Lime_600
+ -5983189 -> R.style.AppTheme_Lime_700
+ -6382300 -> R.style.AppTheme_Lime_800
+ -8227049 -> R.style.AppTheme_Lime_900
+
+ -1596 -> R.style.AppTheme_Yellow_100
+ -2672 -> R.style.AppTheme_Yellow_200
+ -3722 -> R.style.AppTheme_Yellow_300
+ -4520 -> R.style.AppTheme_Yellow_400
+ -5317 -> R.style.AppTheme_Yellow_500
+ -141259 -> R.style.AppTheme_Yellow_600
+ -278483 -> R.style.AppTheme_Yellow_700
+ -415707 -> R.style.AppTheme_Yellow_800
+ -688361 -> R.style.AppTheme_Yellow_900
+
+ -4941 -> R.style.AppTheme_Amber_100
+ -8062 -> R.style.AppTheme_Amber_200
+ -10929 -> R.style.AppTheme_Amber_300
+ -13784 -> R.style.AppTheme_Amber_400
+ -16121 -> R.style.AppTheme_Amber_500
+ -19712 -> R.style.AppTheme_Amber_600
+ -24576 -> R.style.AppTheme_Amber_700
+ -28928 -> R.style.AppTheme_Amber_800
+ -37120 -> R.style.AppTheme_Amber_900
+
+ -8014 -> R.style.AppTheme_Orange_100
+ -13184 -> R.style.AppTheme_Orange_200
+ -18611 -> R.style.AppTheme_Orange_300
+ -22746 -> R.style.AppTheme_Orange_400
+ -26624 -> R.style.AppTheme_Orange_500
+ -291840 -> R.style.AppTheme_Orange_600
+ -689152 -> R.style.AppTheme_Orange_700
+ -1086464 -> R.style.AppTheme_Orange_800
+ -1683200 -> R.style.AppTheme_Orange_900
+
+ -13124 -> R.style.AppTheme_Deep_Orange_100
+ -21615 -> R.style.AppTheme_Deep_Orange_200
+ -30107 -> R.style.AppTheme_Deep_Orange_300
+ -36797 -> R.style.AppTheme_Deep_Orange_400
+ -43230 -> R.style.AppTheme_Deep_Orange_500
+ -765666 -> R.style.AppTheme_Deep_Orange_600
+ -1684967 -> R.style.AppTheme_Deep_Orange_700
+ -2604267 -> R.style.AppTheme_Deep_Orange_800
+ -4246004 -> R.style.AppTheme_Deep_Orange_900
+
+ -2634552 -> R.style.AppTheme_Brown_100
+ -4412764 -> R.style.AppTheme_Brown_200
+ -6190977 -> R.style.AppTheme_Brown_300
+ -7508381 -> R.style.AppTheme_Brown_400
+ -8825528 -> R.style.AppTheme_Brown_500
+ -9614271 -> R.style.AppTheme_Brown_600
+ -10665929 -> R.style.AppTheme_Brown_700
+ -11652050 -> R.style.AppTheme_Brown_800
+ -12703965 -> R.style.AppTheme_Brown_900
+
+ -1 -> R.style.AppTheme_Grey_100
+ -1118482 -> R.style.AppTheme_Grey_200
+ -2039584 -> R.style.AppTheme_Grey_300
+ -4342339 -> R.style.AppTheme_Grey_400
+ -6381922 -> R.style.AppTheme_Grey_500
+ -9079435 -> R.style.AppTheme_Grey_600
+ -10395295 -> R.style.AppTheme_Grey_700
+ -12434878 -> R.style.AppTheme_Grey_800
+ -16777216 -> R.style.AppTheme_Grey_900
+
+ -3155748 -> R.style.AppTheme_Blue_Grey_100
+ -5194811 -> R.style.AppTheme_Blue_Grey_200
+ -7297874 -> R.style.AppTheme_Blue_Grey_300
+ -8875876 -> R.style.AppTheme_Blue_Grey_400
+ -10453621 -> R.style.AppTheme_Blue_Grey_500
+ -11243910 -> R.style.AppTheme_Blue_Grey_600
+ -12232092 -> R.style.AppTheme_Blue_Grey_700
+ -13154481 -> R.style.AppTheme_Blue_Grey_800
+ -14273992 -> R.style.AppTheme_Blue_Grey_900
+
+ else -> R.style.AppTheme_Orange_700
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity.kt
new file mode 100644
index 0000000..465d5d2
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Activity.kt
@@ -0,0 +1,875 @@
+package com.simplemobiletools.commons.extensions
+
+import android.annotation.SuppressLint
+import android.app.Activity
+import android.content.*
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.graphics.drawable.ColorDrawable
+import android.media.RingtoneManager
+import android.net.Uri
+import android.os.Looper
+import android.os.TransactionTooLargeException
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+import android.support.v4.provider.DocumentFile
+import android.support.v7.app.AlertDialog
+import android.support.v7.app.AppCompatActivity
+import android.text.Html
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import android.view.WindowManager
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import android.widget.TextView
+import android.widget.Toast
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+import com.simplemobiletools.commons.dialogs.*
+import com.simplemobiletools.commons.helpers.*
+import com.simplemobiletools.commons.models.*
+import com.simplemobiletools.commons.views.MyTextView
+import kotlinx.android.synthetic.main.dialog_title.view.*
+import java.io.*
+import java.util.*
+
+fun Activity.toast(id: Int, length: Int = Toast.LENGTH_SHORT) {
+ if (isOnMainThread()) {
+ showToast(this, id, length)
+ } else {
+ runOnUiThread {
+ showToast(this, id, length)
+ }
+ }
+}
+
+fun Activity.toast(msg: String, length: Int = Toast.LENGTH_SHORT) {
+ if (isOnMainThread()) {
+ showToast(this, msg, length)
+ } else {
+ runOnUiThread {
+ showToast(this, msg, length)
+ }
+ }
+}
+
+private fun showToast(activity: Activity, messageId: Int, length: Int) {
+ if (!activity.isActivityDestroyed()) {
+ showToast(activity, activity.getString(messageId), length)
+ }
+}
+
+private fun showToast(activity: Activity, message: String, length: Int) {
+ if (!activity.isActivityDestroyed()) {
+ activity.applicationContext.toast(message, length)
+ }
+}
+
+fun Activity.showErrorToast(msg: String, length: Int = Toast.LENGTH_LONG) {
+ toast(String.format(getString(R.string.an_error_occurred), msg), length)
+}
+
+fun Activity.showErrorToast(exception: Exception, length: Int = Toast.LENGTH_LONG) {
+ showErrorToast(exception.toString(), length)
+}
+
+fun AppCompatActivity.updateActionBarTitle(text: String, color: Int = baseConfig.primaryColor) {
+ supportActionBar?.title = Html.fromHtml("$text ")
+}
+
+fun AppCompatActivity.updateActionBarSubtitle(text: String) {
+ supportActionBar?.subtitle = Html.fromHtml("$text ")
+}
+
+@SuppressLint("NewApi")
+fun Activity.appLaunched(appId: String) {
+ baseConfig.internalStoragePath = getInternalStoragePath()
+ updateSDCardPath()
+ baseConfig.appId = appId
+ if (baseConfig.appRunCount == 0) {
+ baseConfig.wasOrangeIconChecked = true
+ checkAppIconColor()
+ } else if (!baseConfig.wasOrangeIconChecked) {
+ baseConfig.wasOrangeIconChecked = true
+ val primaryColor = resources.getColor(R.color.color_primary)
+ if (baseConfig.appIconColor != primaryColor) {
+ getAppIconColors().forEachIndexed { index, color ->
+ toggleAppIconColor(appId, index, color, false)
+ }
+
+ val defaultClassName = "${baseConfig.appId.removeSuffix(".debug")}.activities.SplashActivity"
+ packageManager.setComponentEnabledSetting(ComponentName(baseConfig.appId, defaultClassName), PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, PackageManager.DONT_KILL_APP)
+
+ val orangeClassName = "${baseConfig.appId.removeSuffix(".debug")}.activities.SplashActivity.Orange"
+ packageManager.setComponentEnabledSetting(ComponentName(baseConfig.appId, orangeClassName), PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP)
+
+ baseConfig.appIconColor = primaryColor
+ baseConfig.lastIconColor = primaryColor
+ }
+ }
+ baseConfig.appRunCount++
+
+ if (!baseConfig.hadThankYouInstalled) {
+ if (isThankYouInstalled()) {
+ baseConfig.hadThankYouInstalled = true
+ } else if (baseConfig.appRunCount % 50 == 0) {
+ DonateDialog(this)
+ }
+ }
+}
+
+fun Activity.isAppInstalledOnSDCard(): Boolean = try {
+ val applicationInfo = packageManager.getPackageInfo(packageName, 0).applicationInfo
+ (applicationInfo.flags and ApplicationInfo.FLAG_EXTERNAL_STORAGE) == ApplicationInfo.FLAG_EXTERNAL_STORAGE
+} catch (e: Exception) {
+ false
+}
+
+@SuppressLint("InlinedApi")
+fun Activity.isShowingSAFDialog(path: String, treeUri: String, requestCode: Int): Boolean {
+ return if (needsStupidWritePermissions(path) && (treeUri.isEmpty() || !hasProperStoredTreeUri())) {
+ runOnUiThread {
+ WritePermissionDialog(this, false) {
+ Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
+ putExtra("android.content.extra.SHOW_ADVANCED", true)
+ if (resolveActivity(packageManager) == null) {
+ type = "*/*"
+ }
+
+ if (resolveActivity(packageManager) != null) {
+ startActivityForResult(this, requestCode)
+ } else {
+ toast(R.string.unknown_error_occurred)
+ }
+ }
+ }
+ }
+ true
+ } else {
+ false
+ }
+}
+
+fun Activity.launchPurchaseThankYouIntent() = launchViewIntent(resources.getString(R.string.thank_you_url))
+
+fun Activity.launchViewIntent(id: Int) = launchViewIntent(resources.getString(id))
+
+fun Activity.launchViewIntent(url: String) {
+ Thread {
+ Intent(Intent.ACTION_VIEW, Uri.parse(url)).apply {
+ if (resolveActivity(packageManager) != null) {
+ startActivity(this)
+ } else {
+ toast(R.string.no_app_found)
+ }
+ }
+ }.start()
+}
+
+fun Activity.sharePathIntent(path: String, applicationId: String) {
+ Thread {
+ val newUri = getFinalUriFromPath(path, applicationId) ?: return@Thread
+ Intent().apply {
+ action = Intent.ACTION_SEND
+ putExtra(Intent.EXTRA_STREAM, newUri)
+ type = getUriMimeType(path, newUri)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+
+ try {
+ if (resolveActivity(packageManager) != null) {
+ startActivity(Intent.createChooser(this, getString(R.string.share_via)))
+ } else {
+ toast(R.string.no_app_found)
+ }
+ } catch (e: RuntimeException) {
+ if (e.cause is TransactionTooLargeException) {
+ toast(R.string.maximum_share_reached)
+ } else {
+ showErrorToast(e)
+ }
+ }
+ }
+ }.start()
+}
+
+fun Activity.sharePathsIntent(paths: ArrayList, applicationId: String) {
+ Thread {
+ if (paths.size == 1) {
+ sharePathIntent(paths.first(), applicationId)
+ } else {
+ val uriPaths = ArrayList()
+ val newUris = paths.map {
+ val uri = getFinalUriFromPath(it, applicationId) ?: return@Thread
+ uriPaths.add(uri.path)
+ uri
+ } as ArrayList
+
+ var mimeType = uriPaths.getMimeType()
+ if (mimeType.isEmpty() || mimeType == "*/*") {
+ mimeType = paths.getMimeType()
+ }
+
+ Intent().apply {
+ action = Intent.ACTION_SEND_MULTIPLE
+ type = mimeType
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ putParcelableArrayListExtra(Intent.EXTRA_STREAM, newUris)
+
+ try {
+ if (resolveActivity(packageManager) != null) {
+ startActivity(Intent.createChooser(this, getString(R.string.share_via)))
+ } else {
+ toast(R.string.no_app_found)
+ }
+ } catch (e: RuntimeException) {
+ if (e.cause is TransactionTooLargeException) {
+ toast(R.string.maximum_share_reached)
+ } else {
+ showErrorToast(e)
+ }
+ }
+ }
+ }
+ }.start()
+}
+
+fun Activity.setAsIntent(path: String, applicationId: String) {
+ Thread {
+ val newUri = getFinalUriFromPath(path, applicationId) ?: return@Thread
+ Intent().apply {
+ action = Intent.ACTION_ATTACH_DATA
+ setDataAndType(newUri, getUriMimeType(path, newUri))
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ val chooser = Intent.createChooser(this, getString(R.string.set_as))
+
+ if (resolveActivity(packageManager) != null) {
+ startActivityForResult(chooser, REQUEST_SET_AS)
+ } else {
+ toast(R.string.no_app_found)
+ }
+ }
+ }.start()
+}
+
+fun Activity.openEditorIntent(path: String, applicationId: String) {
+ Thread {
+ val newUri = getFinalUriFromPath(path, applicationId) ?: return@Thread
+ Intent().apply {
+ action = Intent.ACTION_EDIT
+ setDataAndType(newUri, getUriMimeType(path, newUri))
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+
+ val parent = path.getParentPath()
+ val newFilename = "${path.getFilenameFromPath().substringBeforeLast('.')}_1"
+ val extension = path.getFilenameExtension()
+ val newFilePath = File(parent, "$newFilename.$extension")
+
+ val outputUri = if (path.startsWith(OTG_PATH)) newUri else getFinalUriFromPath("$newFilePath", applicationId)
+ val resInfoList = packageManager.queryIntentActivities(this, PackageManager.MATCH_DEFAULT_ONLY)
+ for (resolveInfo in resInfoList) {
+ val packageName = resolveInfo.activityInfo.packageName
+ grantUriPermission(packageName, outputUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+
+ putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
+ putExtra(REAL_FILE_PATH, path)
+
+ if (resolveActivity(packageManager) != null) {
+ try {
+ startActivityForResult(this, REQUEST_EDIT_IMAGE)
+ } catch (e: SecurityException) {
+ showErrorToast(e)
+ }
+ } else {
+ toast(R.string.no_app_found)
+ }
+ }
+ }.start()
+}
+
+fun Activity.openPathIntent(path: String, forceChooser: Boolean, applicationId: String, forceMimeType: String = "") {
+ Thread {
+ val newUri = getFinalUriFromPath(path, applicationId) ?: return@Thread
+ val mimeType = if (forceMimeType.isNotEmpty()) forceMimeType else getUriMimeType(path, newUri)
+ Intent().apply {
+ action = Intent.ACTION_VIEW
+ setDataAndType(newUri, mimeType)
+ addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+
+ if (applicationId == "com.simplemobiletools.gallery" || applicationId == "com.simplemobiletools.gallery.debug") {
+ putExtra(IS_FROM_GALLERY, true)
+ }
+
+ putExtra(REAL_FILE_PATH, path)
+
+ if (resolveActivity(packageManager) != null) {
+ val chooser = Intent.createChooser(this, getString(R.string.open_with))
+ try {
+ startActivity(if (forceChooser) chooser else this)
+ } catch (e: NullPointerException) {
+ showErrorToast(e)
+ }
+ } else {
+ if (!tryGenericMimeType(this, mimeType, newUri)) {
+ toast(R.string.no_app_found)
+ }
+ }
+ }
+ }.start()
+}
+
+fun Activity.getFinalUriFromPath(path: String, applicationId: String): Uri? {
+ val uri = try {
+ ensurePublicUri(path, applicationId)
+ } catch (e: Exception) {
+ showErrorToast(e)
+ return null
+ }
+
+ if (uri == null) {
+ toast(R.string.unknown_error_occurred)
+ return null
+ }
+
+ return uri
+}
+
+fun Activity.tryGenericMimeType(intent: Intent, mimeType: String, uri: Uri): Boolean {
+ var genericMimeType = mimeType.getGenericMimeType()
+ if (genericMimeType.isEmpty()) {
+ genericMimeType = "*/*"
+ }
+
+ intent.setDataAndType(uri, genericMimeType)
+ return if (intent.resolveActivity(packageManager) != null) {
+ startActivity(intent)
+ true
+ } else {
+ false
+ }
+}
+
+fun BaseSimpleActivity.checkWhatsNew(releases: List, currVersion: Int) {
+ if (baseConfig.lastVersion == 0) {
+ baseConfig.lastVersion = currVersion
+ return
+ }
+
+ val newReleases = arrayListOf()
+ releases.filterTo(newReleases) { it.id > baseConfig.lastVersion }
+
+ if (newReleases.isNotEmpty() && !baseConfig.avoidWhatsNew) {
+ WhatsNewDialog(this, newReleases)
+ }
+
+ baseConfig.lastVersion = currVersion
+}
+
+fun BaseSimpleActivity.deleteFolders(folders: ArrayList, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ Thread {
+ deleteFoldersBg(folders, deleteMediaOnly, callback)
+ }.start()
+ } else {
+ deleteFoldersBg(folders, deleteMediaOnly, callback)
+ }
+}
+
+fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
+ var wasSuccess = false
+ var needPermissionForPath = ""
+ for (folder in folders) {
+ if (needsStupidWritePermissions(folder.path) && baseConfig.treeUri.isEmpty()) {
+ needPermissionForPath = folder.path
+ break
+ }
+ }
+
+ handleSAFDialog(needPermissionForPath) {
+ folders.forEachIndexed { index, folder ->
+ deleteFolderBg(folder, deleteMediaOnly) {
+ if (it)
+ wasSuccess = true
+
+ if (index == folders.size - 1) {
+ runOnUiThread {
+ callback?.invoke(wasSuccess)
+ }
+ }
+ }
+ }
+ }
+}
+
+fun BaseSimpleActivity.deleteFolder(folder: FileDirItem, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ Thread {
+ deleteFolderBg(folder, deleteMediaOnly, callback)
+ }.start()
+ } else {
+ deleteFolderBg(folder, deleteMediaOnly, callback)
+ }
+}
+
+fun BaseSimpleActivity.deleteFolderBg(fileDirItem: FileDirItem, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
+ val folder = File(fileDirItem.path)
+ if (folder.exists()) {
+ val filesArr = folder.listFiles()
+ if (filesArr == null) {
+ runOnUiThread {
+ callback?.invoke(true)
+ }
+ return
+ }
+
+ val files = filesArr.toMutableList().filter { !deleteMediaOnly || it.isMediaFile() }
+ for (file in files) {
+ deleteFileBg(file.toFileDirItem(applicationContext), false) { }
+ }
+
+ if (folder.listFiles()?.isEmpty() == true) {
+ deleteFileBg(fileDirItem, true) { }
+ }
+ }
+ runOnUiThread {
+ callback?.invoke(true)
+ }
+}
+
+fun BaseSimpleActivity.deleteFiles(files: ArrayList, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ Thread {
+ deleteFilesBg(files, allowDeleteFolder, callback)
+ }.start()
+ } else {
+ deleteFilesBg(files, allowDeleteFolder, callback)
+ }
+}
+
+fun BaseSimpleActivity.deleteFilesBg(files: ArrayList, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
+ if (files.isEmpty()) {
+ runOnUiThread {
+ callback?.invoke(true)
+ }
+ return
+ }
+
+ var wasSuccess = false
+ handleSAFDialog(files[0].path) {
+ files.forEachIndexed { index, file ->
+ deleteFileBg(file, allowDeleteFolder) {
+ if (it) {
+ wasSuccess = true
+ }
+
+ if (index == files.size - 1) {
+ runOnUiThread {
+ callback?.invoke(wasSuccess)
+ }
+ }
+ }
+ }
+ }
+}
+
+fun BaseSimpleActivity.deleteFile(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ Thread {
+ deleteFileBg(fileDirItem, allowDeleteFolder, callback)
+ }.start()
+ } else {
+ deleteFileBg(fileDirItem, allowDeleteFolder, callback)
+ }
+}
+
+@SuppressLint("NewApi")
+fun BaseSimpleActivity.deleteFileBg(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
+ val path = fileDirItem.path
+ val file = File(path)
+ var fileDeleted = !path.startsWith(OTG_PATH) && ((!file.exists() && file.length() == 0L) || file.delete())
+ if (fileDeleted) {
+ rescanDeletedPath(path) {
+ runOnUiThread {
+ callback?.invoke(true)
+ }
+ }
+ } else {
+ if (file.isDirectory && allowDeleteFolder) {
+ fileDeleted = deleteRecursively(file)
+ }
+
+ if (!fileDeleted) {
+ if (isPathOnSD(path)) {
+ handleSAFDialog(path) {
+ trySAFFileDelete(fileDirItem, allowDeleteFolder, callback)
+ }
+ } else if (path.startsWith(OTG_PATH)) {
+ trySAFFileDelete(fileDirItem, allowDeleteFolder, callback)
+ }
+ }
+ }
+}
+
+private fun deleteRecursively(file: File): Boolean {
+ if (file.isDirectory) {
+ val files = file.listFiles() ?: return file.delete()
+ for (child in files) {
+ deleteRecursively(child)
+ }
+ }
+
+ return file.delete()
+}
+
+fun Activity.scanFileRecursively(file: File, callback: (() -> Unit)? = null) {
+ applicationContext.scanFileRecursively(file, callback)
+}
+
+fun Activity.scanPathRecursively(path: String, callback: (() -> Unit)? = null) {
+ applicationContext.scanPathRecursively(path, callback)
+}
+
+fun Activity.scanFilesRecursively(files: ArrayList, callback: (() -> Unit)? = null) {
+ applicationContext.scanFilesRecursively(files, callback)
+}
+
+fun Activity.scanPathsRecursively(paths: ArrayList, callback: (() -> Unit)? = null) {
+ applicationContext.scanPathsRecursively(paths, callback)
+}
+
+fun Activity.rescanPaths(paths: ArrayList, callback: (() -> Unit)? = null) {
+ applicationContext.rescanPaths(paths, callback)
+}
+
+@SuppressLint("NewApi")
+fun BaseSimpleActivity.renameFile(oldPath: String, newPath: String, callback: ((success: Boolean) -> Unit)? = null) {
+ if (needsStupidWritePermissions(newPath)) {
+ handleSAFDialog(newPath) {
+ val document = getDocumentFile(oldPath)
+ if (document == null || (File(oldPath).isDirectory != document.isDirectory)) {
+ runOnUiThread {
+ callback?.invoke(false)
+ }
+ return@handleSAFDialog
+ }
+
+ try {
+ val uri = DocumentsContract.renameDocument(applicationContext.contentResolver, document.uri, newPath.getFilenameFromPath())
+ if (document.uri != uri) {
+ updateInMediaStore(oldPath, newPath)
+ rescanPaths(arrayListOf(oldPath, newPath)) {
+ if (!baseConfig.keepLastModified) {
+ updateLastModified(newPath, System.currentTimeMillis())
+ }
+ runOnUiThread {
+ callback?.invoke(true)
+ }
+ }
+ } else {
+ runOnUiThread {
+ callback?.invoke(false)
+ }
+ }
+ } catch (e: SecurityException) {
+ showErrorToast(e)
+ runOnUiThread {
+ callback?.invoke(false)
+ }
+ }
+ }
+ } else if (File(oldPath).renameTo(File(newPath))) {
+ if (File(newPath).isDirectory) {
+ deleteFromMediaStore(oldPath)
+ rescanPaths(arrayListOf(newPath)) {
+ runOnUiThread {
+ callback?.invoke(true)
+ }
+ scanPathRecursively(newPath)
+ }
+ } else {
+ if (!baseConfig.keepLastModified) {
+ File(newPath).setLastModified(System.currentTimeMillis())
+ }
+ scanPathsRecursively(arrayListOf(newPath)) {
+ runOnUiThread {
+ callback?.invoke(true)
+ }
+ }
+ }
+ } else {
+ runOnUiThread {
+ callback?.invoke(false)
+ }
+ }
+}
+
+fun Activity.hideKeyboard() {
+ val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ inputMethodManager.hideSoftInputFromWindow((currentFocus ?: View(this)).windowToken, 0)
+ window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
+ currentFocus?.clearFocus()
+}
+
+fun Activity.showKeyboard(et: EditText) {
+ val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
+ imm.showSoftInput(et, InputMethodManager.SHOW_IMPLICIT)
+}
+
+fun Activity.hideKeyboard(view: View) {
+ val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
+ inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
+}
+
+fun BaseSimpleActivity.getFileOutputStream(fileDirItem: FileDirItem, allowCreatingNewFile: Boolean = false, callback: (outputStream: OutputStream?) -> Unit) {
+ if (needsStupidWritePermissions(fileDirItem.path)) {
+ handleSAFDialog(fileDirItem.path) {
+ var document = getDocumentFile(fileDirItem.path)
+ if (document == null && allowCreatingNewFile) {
+ document = getDocumentFile(fileDirItem.getParentPath())
+ }
+
+ if (document == null) {
+ val error = String.format(getString(R.string.could_not_create_file), fileDirItem.path)
+ showErrorToast(error)
+ callback(null)
+ return@handleSAFDialog
+ }
+
+ if (!File(fileDirItem.path).exists()) {
+ document = document.createFile("", fileDirItem.name)
+ }
+
+ if (document?.exists() == true) {
+ try {
+ callback(applicationContext.contentResolver.openOutputStream(document.uri))
+ } catch (e: FileNotFoundException) {
+ showErrorToast(e)
+ callback(null)
+ }
+ } else {
+ val error = String.format(getString(R.string.could_not_create_file), fileDirItem.path)
+ showErrorToast(error)
+ callback(null)
+ }
+ }
+ } else {
+ val file = File(fileDirItem.path)
+ if (!file.parentFile.exists()) {
+ file.parentFile.mkdirs()
+ }
+
+ try {
+ callback(FileOutputStream(file))
+ } catch (e: Exception) {
+ callback(null)
+ }
+ }
+}
+
+fun BaseSimpleActivity.getFileOutputStreamSync(path: String, mimeType: String, parentDocumentFile: DocumentFile? = null): OutputStream? {
+ val targetFile = File(path)
+
+ return if (needsStupidWritePermissions(path)) {
+ var documentFile = parentDocumentFile
+ if (documentFile == null) {
+ if (targetFile.parentFile.exists()) {
+ documentFile = getDocumentFile(targetFile.parentFile.absolutePath)
+ } else {
+ documentFile = getDocumentFile(targetFile.parentFile.parent)
+ documentFile = documentFile!!.createDirectory(targetFile.parentFile.name)
+ }
+ }
+
+ if (documentFile == null) {
+ val error = String.format(getString(R.string.could_not_create_file), targetFile.parent)
+ showErrorToast(error)
+ return null
+ }
+
+ val newDocument = documentFile.createFile(mimeType, path.getFilenameFromPath())
+ applicationContext.contentResolver.openOutputStream(newDocument!!.uri)
+ } else {
+ if (!targetFile.parentFile.exists()) {
+ targetFile.parentFile.mkdirs()
+ }
+
+ try {
+ FileOutputStream(targetFile)
+ } catch (e: Exception) {
+ showErrorToast(e)
+ null
+ }
+ }
+}
+
+fun BaseSimpleActivity.getFileInputStreamSync(path: String): InputStream? {
+ return if (path.startsWith(OTG_PATH)) {
+ val fileDocument = getSomeDocumentFile(path)
+ applicationContext.contentResolver.openInputStream(fileDocument?.uri)
+ } else {
+ FileInputStream(File(path))
+ }
+}
+
+
+
+fun BaseSimpleActivity.createDirectorySync(directory: String): Boolean {
+ if (getDoesFilePathExist(directory)) {
+ return true
+ }
+
+ if (needsStupidWritePermissions(directory)) {
+ val documentFile = getDocumentFile(directory.getParentPath()) ?: return false
+ val newDir = documentFile.createDirectory(directory.getFilenameFromPath())
+ return newDir != null
+ }
+
+ return File(directory).mkdirs()
+}
+
+fun Activity.isActivityDestroyed() = isJellyBean1Plus() && isDestroyed
+
+fun Activity.updateSharedTheme(sharedTheme: SharedTheme) {
+ try {
+ val contentValues = MyContentProvider.fillThemeContentValues(sharedTheme)
+ applicationContext.contentResolver.update(MyContentProvider.MY_CONTENT_URI, contentValues, null, null)
+ } catch (e: Exception) {
+ showErrorToast(e)
+ }
+}
+
+fun Activity.copyToClipboard(text: String) {
+ val clip = ClipData.newPlainText(getString(R.string.simple_commons), text)
+ (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).primaryClip = clip
+ toast(R.string.value_copied_to_clipboard)
+}
+
+fun Activity.setupDialogStuff(view: View, dialog: AlertDialog, titleId: Int = 0, titleText: String = "", callback: (() -> Unit)? = null) {
+ if (isActivityDestroyed()) {
+ return
+ }
+
+ if (view is ViewGroup)
+ updateTextColors(view)
+ else if (view is MyTextView) {
+ view.setColors(baseConfig.textColor, getAdjustedPrimaryColor(), baseConfig.backgroundColor)
+ }
+
+ var title: TextView? = null
+ if (titleId != 0 || titleText.isNotEmpty()) {
+ title = layoutInflater.inflate(R.layout.dialog_title, null) as TextView
+ title.dialog_title_textview.apply {
+ if (titleText.isNotEmpty()) {
+ text = titleText
+ } else {
+ setText(titleId)
+ }
+ setTextColor(baseConfig.textColor)
+ }
+ }
+
+ dialog.apply {
+ setView(view)
+ requestWindowFeature(Window.FEATURE_NO_TITLE)
+ setCustomTitle(title)
+ setCanceledOnTouchOutside(true)
+ show()
+ getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(baseConfig.textColor)
+ getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(baseConfig.textColor)
+ getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(baseConfig.textColor)
+ window.setBackgroundDrawable(ColorDrawable(baseConfig.backgroundColor))
+ }
+ callback?.invoke()
+}
+
+fun Activity.showPickSecondsDialogHelper(curMinutes: Int, isSnoozePicker: Boolean = false, showSecondsAtCustomDialog: Boolean = false,
+ cancelCallback: (() -> Unit)? = null, callback: (seconds: Int) -> Unit) {
+ val seconds = if (curMinutes > 0) curMinutes * 60 else curMinutes
+ showPickSecondsDialog(seconds, isSnoozePicker, showSecondsAtCustomDialog, cancelCallback, callback)
+}
+
+fun Activity.showPickSecondsDialog(curSeconds: Int, isSnoozePicker: Boolean = false, showSecondsAtCustomDialog: Boolean = false,
+ cancelCallback: (() -> Unit)? = null, callback: (seconds: Int) -> Unit) {
+ hideKeyboard()
+ val seconds = TreeSet()
+ seconds.apply {
+ if (!isSnoozePicker) {
+ add(-1)
+ add(0)
+ }
+ add(1 * MINUTE_SECONDS)
+ add(5 * MINUTE_SECONDS)
+ add(10 * MINUTE_SECONDS)
+ add(30 * MINUTE_SECONDS)
+ add(60 * MINUTE_SECONDS)
+ add(curSeconds)
+ }
+
+ val items = ArrayList(seconds.size + 1)
+ seconds.mapIndexedTo(items, { index, value ->
+ RadioItem(index, getFormattedSeconds(value, !isSnoozePicker), value)
+ })
+
+ var selectedIndex = 0
+ seconds.forEachIndexed { index, value ->
+ if (value == curSeconds) {
+ selectedIndex = index
+ }
+ }
+
+ items.add(RadioItem(-2, getString(R.string.custom)))
+
+ RadioGroupDialog(this, items, selectedIndex, showOKButton = isSnoozePicker, cancelCallback = cancelCallback) {
+ if (it == -2) {
+ CustomIntervalPickerDialog(this, showSeconds = showSecondsAtCustomDialog) {
+ callback(it)
+ }
+ } else {
+ callback(it as Int)
+ }
+ }
+}
+
+fun BaseSimpleActivity.getAlarmSounds(type: Int, callback: (ArrayList) -> Unit) {
+ val alarms = ArrayList()
+ val manager = RingtoneManager(this)
+ manager.setType(if (type == ALARM_SOUND_TYPE_NOTIFICATION) RingtoneManager.TYPE_NOTIFICATION else RingtoneManager.TYPE_ALARM)
+
+ try {
+ val cursor = manager.cursor
+ var curId = 1
+ val silentAlarm = AlarmSound(curId++, getString(R.string.no_sound), SILENT)
+ alarms.add(silentAlarm)
+
+ val defaultAlarm = getDefaultAlarmSound(type)
+ alarms.add(defaultAlarm)
+
+ while (cursor.moveToNext()) {
+ val title = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)
+ var uri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX)
+ val id = cursor.getString(RingtoneManager.ID_COLUMN_INDEX)
+ if (!uri.endsWith(id)) {
+ uri += "/$id"
+ }
+
+ val alarmSound = AlarmSound(curId++, title, uri)
+ alarms.add(alarmSound)
+ }
+ callback(alarms)
+ } catch (e: Exception) {
+ if (e is SecurityException) {
+ handlePermission(PERMISSION_READ_STORAGE) {
+ if (it) {
+ getAlarmSounds(type, callback)
+ } else {
+ showErrorToast(e)
+ callback(ArrayList())
+ }
+ }
+ } else {
+ showErrorToast(e)
+ callback(ArrayList())
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/AlertDialog.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/AlertDialog.kt
new file mode 100644
index 0000000..f9bfcb5
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/AlertDialog.kt
@@ -0,0 +1,18 @@
+package com.simplemobiletools.commons.extensions
+
+import android.support.v7.app.AlertDialog
+import android.view.WindowManager
+import android.widget.EditText
+
+fun AlertDialog.showKeyboard(editText: EditText) {
+ window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
+ editText.apply {
+ onGlobalLayout {
+ setSelection(text.toString().length)
+ }
+ }
+}
+
+fun AlertDialog.hideKeyboard() {
+ window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/App.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/App.kt
new file mode 100644
index 0000000..b1d81b5
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/App.kt
@@ -0,0 +1,12 @@
+package com.simplemobiletools.commons.extensions
+
+import android.app.Application
+import java.util.*
+
+fun Application.checkUseEnglish() {
+ if (baseConfig.useEnglish) {
+ val conf = resources.configuration
+ conf.locale = Locale.ENGLISH
+ resources.updateConfiguration(conf, resources.displayMetrics)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ArrayList.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ArrayList.kt
new file mode 100644
index 0000000..71b972a
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ArrayList.kt
@@ -0,0 +1,8 @@
+package com.simplemobiletools.commons.extensions
+
+import java.util.*
+
+fun ArrayList.moveLastItemToFront() {
+ val last = removeAt(size - 1)
+ add(0, last)
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/BufferedWriter.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/BufferedWriter.kt
new file mode 100644
index 0000000..fb70f83
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/BufferedWriter.kt
@@ -0,0 +1,8 @@
+package com.simplemobiletools.commons.extensions
+
+import java.io.BufferedWriter
+
+fun BufferedWriter.writeLn(line: String) {
+ write(line)
+ newLine()
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context-storage.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context-storage.kt
new file mode 100644
index 0000000..45dbcb5
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context-storage.kt
@@ -0,0 +1,448 @@
+package com.simplemobiletools.commons.extensions
+
+import android.annotation.SuppressLint
+import android.content.ContentValues
+import android.content.Context
+import android.hardware.usb.UsbManager
+import android.media.MediaScannerConnection
+import android.net.Uri
+import android.os.Environment
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+import android.support.v4.content.FileProvider
+import android.support.v4.provider.DocumentFile
+import android.text.TextUtils
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.helpers.OTG_PATH
+import com.simplemobiletools.commons.helpers.isLollipopPlus
+import com.simplemobiletools.commons.helpers.isMarshmallowPlus
+import com.simplemobiletools.commons.helpers.isNougatPlus
+import com.simplemobiletools.commons.models.FileDirItem
+import java.io.File
+import java.net.URLDecoder
+import java.util.*
+import java.util.regex.Pattern
+
+// http://stackoverflow.com/a/40582634/1967672
+fun Context.getSDCardPath(): String {
+ val directories = getStorageDirectories().filter { it.trimEnd('/') != getInternalStoragePath() }
+ var sdCardPath = directories.firstOrNull { !physicalPaths.contains(it.toLowerCase().trimEnd('/')) } ?: ""
+
+ // on some devices no method retrieved any SD card path, so test if its not sdcard1 by any chance. It happened on an Android 5.1
+ if (sdCardPath.trimEnd('/').isEmpty()) {
+ val file = File("/storage/sdcard1")
+ if (file.exists()) {
+ return file.absolutePath
+ }
+
+ sdCardPath = directories.firstOrNull() ?: ""
+ }
+
+ if (sdCardPath.isEmpty()) {
+ val SDpattern = Pattern.compile("^[A-Za-z0-9]{4}-[A-Za-z0-9]{4}$")
+ try {
+ File("/storage").listFiles()?.forEach {
+ if (SDpattern.matcher(it.name).matches()) {
+ sdCardPath = "/storage/${it.name}"
+ }
+ }
+ } catch (e: Exception) {
+ }
+ }
+
+ val finalPath = sdCardPath.trimEnd('/')
+ baseConfig.sdCardPath = finalPath
+ return finalPath
+}
+
+fun Context.hasExternalSDCard() = sdCardPath.isNotEmpty()
+
+fun Context.hasOTGConnected() = (getSystemService(Context.USB_SERVICE) as UsbManager).deviceList.isNotEmpty()
+
+@SuppressLint("NewApi")
+fun Context.getStorageDirectories(): Array {
+ val paths = HashSet()
+ val rawExternalStorage = System.getenv("EXTERNAL_STORAGE")
+ val rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE")
+ val rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET")
+ if (TextUtils.isEmpty(rawEmulatedStorageTarget)) {
+ if (isMarshmallowPlus()) {
+ getExternalFilesDirs(null).filterNotNull().map { it.absolutePath }
+ .mapTo(paths) { it.substring(0, it.indexOf("Android/data")) }
+ } else {
+ if (TextUtils.isEmpty(rawExternalStorage)) {
+ paths.addAll(physicalPaths)
+ } else {
+ paths.add(rawExternalStorage)
+ }
+ }
+ } else {
+ val path = Environment.getExternalStorageDirectory().absolutePath
+ val folders = Pattern.compile("/").split(path)
+ val lastFolder = folders[folders.size - 1]
+ var isDigit = false
+ try {
+ Integer.valueOf(lastFolder)
+ isDigit = true
+ } catch (ignored: NumberFormatException) {
+ }
+
+ val rawUserId = if (isDigit) lastFolder else ""
+ if (TextUtils.isEmpty(rawUserId)) {
+ paths.add(rawEmulatedStorageTarget)
+ } else {
+ paths.add(rawEmulatedStorageTarget + File.separator + rawUserId)
+ }
+ }
+
+ if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
+ val rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator.toRegex()).dropLastWhile(String::isEmpty).toTypedArray()
+ Collections.addAll(paths, *rawSecondaryStorages)
+ }
+ return paths.toTypedArray()
+}
+
+fun Context.getHumanReadablePath(path: String): String {
+ return getString(when (path) {
+ "/" -> R.string.root
+ internalStoragePath -> R.string.internal
+ OTG_PATH -> R.string.otg
+ else -> R.string.sd_card
+ })
+}
+
+fun Context.humanizePath(path: String): String {
+ val trimmedPath = path.trimEnd('/')
+ val basePath = path.getBasePath(this)
+ return when (basePath) {
+ "/" -> "${getHumanReadablePath(basePath)}$trimmedPath"
+ OTG_PATH -> path.replaceFirst(basePath, "${getHumanReadablePath(basePath).trimEnd('/')}/").replace("//", "/")
+ else -> trimmedPath.replaceFirst(basePath, getHumanReadablePath(basePath))
+ }
+}
+
+fun Context.getInternalStoragePath() = Environment.getExternalStorageDirectory().absolutePath.trimEnd('/')
+
+fun Context.isPathOnSD(path: String) = sdCardPath.isNotEmpty() && path.startsWith(sdCardPath)
+
+fun Context.needsStupidWritePermissions(path: String) = (isPathOnSD(path) || path.startsWith(OTG_PATH)) && isLollipopPlus()
+
+@SuppressLint("NewApi")
+fun Context.hasProperStoredTreeUri(): Boolean {
+ val hasProperUri = contentResolver.persistedUriPermissions.any { it.uri.toString() == baseConfig.treeUri }
+ if (!hasProperUri) {
+ baseConfig.treeUri = ""
+ }
+ return hasProperUri
+}
+
+fun Context.isAStorageRootFolder(path: String): Boolean {
+ val trimmed = path.trimEnd('/')
+ return trimmed.isEmpty() || trimmed == internalStoragePath || trimmed == sdCardPath
+}
+
+fun Context.getMyFileUri(file: File): Uri {
+ return if (isNougatPlus()) {
+ FileProvider.getUriForFile(this, "$packageName.provider", file)
+ } else {
+ Uri.fromFile(file)
+ }
+}
+
+@SuppressLint("NewApi")
+fun Context.tryFastDocumentDelete(path: String, allowDeleteFolder: Boolean): Boolean {
+ val document = getFastDocumentFile(path)
+ return if (document?.isFile == true || allowDeleteFolder) {
+ try {
+ DocumentsContract.deleteDocument(contentResolver, document?.uri)
+ } catch (e: Exception) {
+ false
+ }
+ } else {
+ false
+ }
+}
+
+@SuppressLint("NewApi")
+fun Context.getFastDocumentFile(path: String): DocumentFile? {
+ if (!isLollipopPlus()) {
+ return null
+ }
+
+ if (path.startsWith(OTG_PATH)) {
+ return getOTGFastDocumentFile(path)
+ }
+
+ if (baseConfig.sdCardPath.isEmpty()) {
+ return null
+ }
+
+ val relativePath = Uri.encode(path.substring(baseConfig.sdCardPath.length).trim('/'))
+ val externalPathPart = baseConfig.sdCardPath.split("/").lastOrNull(String::isNotEmpty)?.trim('/') ?: return null
+ val fullUri = "${baseConfig.treeUri}/document/$externalPathPart%3A$relativePath"
+ return DocumentFile.fromSingleUri(this, Uri.parse(fullUri))
+}
+
+fun Context.getOTGFastDocumentFile(path: String): DocumentFile? {
+ if (baseConfig.OTGTreeUri.isEmpty()) {
+ return null
+ }
+
+ if (baseConfig.OTGPartition.isEmpty()) {
+ baseConfig.OTGPartition = baseConfig.OTGTreeUri.removeSuffix("%3A").substringAfterLast('/')
+ }
+
+ val relativePath = Uri.encode(path.substring(OTG_PATH.length).trim('/'))
+ val fullUri = "${baseConfig.OTGTreeUri}/document/${baseConfig.OTGPartition}%3A$relativePath"
+ return DocumentFile.fromSingleUri(this, Uri.parse(fullUri))
+}
+
+@SuppressLint("NewApi")
+fun Context.getDocumentFile(path: String): DocumentFile? {
+ if (!isLollipopPlus()) {
+ return null
+ }
+
+ val isOTG = path.startsWith(OTG_PATH)
+ var relativePath = path.substring(if (isOTG) OTG_PATH.length else sdCardPath.length)
+ if (relativePath.startsWith(File.separator)) {
+ relativePath = relativePath.substring(1)
+ }
+
+ var document = DocumentFile.fromTreeUri(applicationContext, Uri.parse(if (isOTG) baseConfig.OTGTreeUri else baseConfig.treeUri))
+ val parts = relativePath.split("/").filter { it.isNotEmpty() }
+ for (part in parts) {
+ document = document?.findFile(part)
+ }
+
+ return document
+}
+
+fun Context.getSomeDocumentFile(path: String) = getFastDocumentFile(path) ?: getDocumentFile(path)
+
+fun Context.scanFileRecursively(file: File, callback: (() -> Unit)? = null) {
+ scanFilesRecursively(arrayListOf(file), callback)
+}
+
+fun Context.scanPathRecursively(path: String, callback: (() -> Unit)? = null) {
+ scanPathsRecursively(arrayListOf(path), callback)
+}
+
+fun Context.scanFilesRecursively(files: ArrayList, callback: (() -> Unit)? = null) {
+ val allPaths = ArrayList()
+ for (file in files) {
+ allPaths.addAll(getPaths(file))
+ }
+ rescanPaths(allPaths, callback)
+}
+
+fun Context.scanPathsRecursively(paths: ArrayList, callback: (() -> Unit)? = null) {
+ val allPaths = ArrayList()
+ for (path in paths) {
+ allPaths.addAll(getPaths(File(path)))
+ }
+ rescanPaths(allPaths, callback)
+}
+
+fun Context.rescanPaths(paths: ArrayList, callback: (() -> Unit)? = null) {
+ var cnt = paths.size
+ MediaScannerConnection.scanFile(applicationContext, paths.toTypedArray(), null) { s, uri ->
+ if (--cnt == 0) {
+ callback?.invoke()
+ }
+ }
+}
+
+fun getPaths(file: File): ArrayList {
+ val paths = arrayListOf(file.absolutePath)
+ if (file.isDirectory) {
+ val files = file.listFiles() ?: return paths
+ for (curFile in files) {
+ paths.addAll(getPaths(curFile))
+ }
+ }
+ return paths
+}
+
+fun Context.getFileUri(path: String) = when {
+ path.isImageSlow() -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ path.isVideoSlow() -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ else -> MediaStore.Files.getContentUri("external")
+}
+
+// these functions update the mediastore instantly, MediaScannerConnection.scanFileRecursively takes some time to really get applied
+fun Context.deleteFromMediaStore(path: String): Boolean {
+ if (getDoesFilePathExist(path) || getIsPathDirectory(path)) {
+ return false
+ }
+
+ return try {
+ val where = "${MediaStore.MediaColumns.DATA} = ?"
+ val args = arrayOf(path)
+ contentResolver.delete(getFileUri(path), where, args) == 1
+ } catch (e: Exception) {
+ false
+ }
+}
+
+fun Context.updateInMediaStore(oldPath: String, newPath: String) {
+ Thread {
+ val values = ContentValues().apply {
+ put(MediaStore.MediaColumns.DATA, newPath)
+ put(MediaStore.MediaColumns.DISPLAY_NAME, newPath.getFilenameFromPath())
+ put(MediaStore.MediaColumns.TITLE, newPath.getFilenameFromPath())
+ }
+ val uri = getFileUri(oldPath)
+ val selection = "${MediaStore.MediaColumns.DATA} = ?"
+ val selectionArgs = arrayOf(oldPath)
+
+ try {
+ contentResolver.update(uri, values, selection, selectionArgs)
+ } catch (ignored: Exception) {
+ }
+ }.start()
+}
+
+fun Context.updateLastModified(path: String, lastModified: Long) {
+ val values = ContentValues().apply {
+ put(MediaStore.MediaColumns.DATE_MODIFIED, lastModified / 1000)
+ }
+ File(path).setLastModified(lastModified)
+ val uri = getFileUri(path)
+ val selection = "${MediaStore.MediaColumns.DATA} = ?"
+ val selectionArgs = arrayOf(path)
+
+ try {
+ contentResolver.update(uri, values, selection, selectionArgs)
+ } catch (ignored: Exception) {
+ }
+}
+
+fun Context.getOTGItems(path: String, shouldShowHidden: Boolean, getProperFileSize: Boolean, callback: (ArrayList) -> Unit) {
+ val items = ArrayList()
+ val OTGTreeUri = baseConfig.OTGTreeUri
+ var rootUri = DocumentFile.fromTreeUri(applicationContext, Uri.parse(OTGTreeUri))
+ if (rootUri == null) {
+ callback(items)
+ return
+ }
+
+ val parts = path.split("/").dropLastWhile { it.isEmpty() }
+ for (part in parts) {
+ if (path == OTG_PATH) {
+ break
+ }
+
+ if (part == "otg:" || part == "") {
+ continue
+ }
+
+ val file = rootUri!!.findFile(part)
+ if (file != null) {
+ rootUri = file
+ }
+ }
+
+ val files = rootUri!!.listFiles().filter { it.exists() }
+
+ val basePath = "${baseConfig.OTGTreeUri}/document/${baseConfig.OTGPartition}%3A"
+ for (file in files) {
+ val name = file.name
+ if (!shouldShowHidden && name!!.startsWith(".")) {
+ continue
+ }
+
+ val isDirectory = file.isDirectory
+ val filePath = file.uri.toString().substring(basePath.length)
+ val decodedPath = OTG_PATH + "/" + URLDecoder.decode(filePath, "UTF-8")
+ val fileSize = when {
+ getProperFileSize -> file.getItemSize(shouldShowHidden)
+ isDirectory -> 0L
+ else -> file.length()
+ }
+
+ val childrenCount = if (isDirectory) {
+ file.listFiles()?.size ?: 0
+ } else {
+ 0
+ }
+
+ val fileDirItem = FileDirItem(decodedPath, name!!, isDirectory, childrenCount, fileSize)
+ items.add(fileDirItem)
+ }
+
+ callback(items)
+}
+
+fun Context.rescanDeletedPath(path: String, callback: (() -> Unit)? = null) {
+ if (path.startsWith(filesDir.toString())) {
+ callback?.invoke()
+ return
+ }
+
+ if (deleteFromMediaStore(path)) {
+ callback?.invoke()
+ } else {
+ if (getIsPathDirectory(path)) {
+ callback?.invoke()
+ return
+ }
+
+ MediaScannerConnection.scanFile(applicationContext, arrayOf(path), null) { s, uri ->
+ try {
+ applicationContext.contentResolver.delete(uri, null, null)
+ } catch (e: Exception) {
+ }
+ callback?.invoke()
+ }
+ }
+}
+
+@SuppressLint("NewApi")
+fun Context.trySAFFileDelete(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
+ var fileDeleted = tryFastDocumentDelete(fileDirItem.path, allowDeleteFolder)
+ if (!fileDeleted) {
+ val document = getDocumentFile(fileDirItem.path)
+ if (document != null && (fileDirItem.isDirectory == document.isDirectory)) {
+ try {
+ fileDeleted = (document.isFile == true || allowDeleteFolder) && DocumentsContract.deleteDocument(applicationContext.contentResolver, document.uri)
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+
+ if (fileDeleted) {
+ rescanDeletedPath(fileDirItem.path) {
+ callback?.invoke(true)
+ }
+ }
+}
+
+fun Context.getDoesFilePathExist(path: String) = if (path.startsWith(OTG_PATH)) getOTGFastDocumentFile(path)?.exists()
+ ?: false else File(path).exists()
+
+fun Context.getIsPathDirectory(path: String): Boolean {
+ return if (path.startsWith(OTG_PATH)) {
+ getOTGFastDocumentFile(path)?.isDirectory ?: false
+ } else {
+ File(path).isDirectory
+ }
+}
+
+// avoid these being set as SD card paths
+private val physicalPaths = arrayListOf(
+ "/storage/sdcard1", // Motorola Xoom
+ "/storage/extsdcard", // Samsung SGS3
+ "/storage/sdcard0/external_sdcard", // User request
+ "/mnt/extsdcard", "/mnt/sdcard/external_sd", // Samsung galaxy family
+ "/mnt/external_sd", "/mnt/media_rw/sdcard1", // 4.4.2 on CyanogenMod S3
+ "/removable/microsd", // Asus transformer prime
+ "/mnt/emmc", "/storage/external_SD", // LG
+ "/storage/ext_sd", // HTC One Max
+ "/storage/removable/sdcard1", // Sony Xperia Z1
+ "/data/sdext", "/data/sdext2", "/data/sdext3", "/data/sdext4", "/sdcard1", // Sony Xperia Z
+ "/sdcard2", // HTC One M8s
+ "/storage/usbdisk0",
+ "/storage/usbdisk1",
+ "/storage/usbdisk2"
+)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context.kt
new file mode 100644
index 0000000..5015eef
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Context.kt
@@ -0,0 +1,569 @@
+package com.simplemobiletools.commons.extensions
+
+import android.Manifest
+import android.annotation.SuppressLint
+import android.content.ComponentName
+import android.content.ContentUris
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.database.Cursor
+import android.graphics.Color
+import android.media.ExifInterface
+import android.media.RingtoneManager
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.provider.BaseColumns
+import android.provider.DocumentsContract
+import android.provider.MediaStore
+import android.provider.OpenableColumns
+import android.support.annotation.RequiresApi
+import android.support.v4.content.ContextCompat
+import android.support.v4.content.CursorLoader
+import android.support.v4.content.FileProvider
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.helpers.*
+import com.simplemobiletools.commons.helpers.MyContentProvider.Companion.COL_APP_ICON_COLOR
+import com.simplemobiletools.commons.helpers.MyContentProvider.Companion.COL_BACKGROUND_COLOR
+import com.simplemobiletools.commons.helpers.MyContentProvider.Companion.COL_LAST_UPDATED_TS
+import com.simplemobiletools.commons.helpers.MyContentProvider.Companion.COL_PRIMARY_COLOR
+import com.simplemobiletools.commons.helpers.MyContentProvider.Companion.COL_TEXT_COLOR
+import com.simplemobiletools.commons.models.AlarmSound
+import com.simplemobiletools.commons.models.SharedTheme
+import com.simplemobiletools.commons.views.*
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
+
+fun Context.getSharedPrefs() = getSharedPreferences(PREFS_KEY, Context.MODE_PRIVATE)
+
+val Context.isRTLLayout: Boolean get() = if (isJellyBean1Plus()) resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL else false
+
+fun Context.updateTextColors(viewGroup: ViewGroup, tmpTextColor: Int = 0, tmpAccentColor: Int = 0) {
+ val textColor = if (tmpTextColor == 0) baseConfig.textColor else tmpTextColor
+ val backgroundColor = baseConfig.backgroundColor
+ val accentColor = if (tmpAccentColor == 0) {
+ if (isBlackAndWhiteTheme()) {
+ Color.WHITE
+ } else {
+ baseConfig.primaryColor
+ }
+ } else {
+ tmpAccentColor
+ }
+
+ val cnt = viewGroup.childCount
+ (0 until cnt).map { viewGroup.getChildAt(it) }
+ .forEach {
+ when (it) {
+ is MyTextView -> it.setColors(textColor, accentColor, backgroundColor)
+ is MyAppCompatSpinner -> it.setColors(textColor, accentColor, backgroundColor)
+ is MySwitchCompat -> it.setColors(textColor, accentColor, backgroundColor)
+ is MyCompatRadioButton -> it.setColors(textColor, accentColor, backgroundColor)
+ is MyAppCompatCheckbox -> it.setColors(textColor, accentColor, backgroundColor)
+ is MyEditText -> it.setColors(textColor, accentColor, backgroundColor)
+ // is MyFloatingActionButton -> it.setColors(textColor, accentColor, backgroundColor)
+ is MySeekBar -> it.setColors(textColor, accentColor, backgroundColor)
+ is MyButton -> it.setColors(textColor, accentColor, backgroundColor)
+ is ViewGroup -> updateTextColors(it, textColor, accentColor)
+ }
+ }
+}
+
+fun Context.getLinkTextColor(): Int {
+ return if (baseConfig.primaryColor == resources.getColor(R.color.color_primary)) {
+ baseConfig.primaryColor
+ } else {
+ baseConfig.textColor
+ }
+}
+
+fun Context.isBlackAndWhiteTheme() = baseConfig.textColor == Color.WHITE && baseConfig.primaryColor == Color.BLACK && baseConfig.backgroundColor == Color.BLACK
+
+fun Context.getAdjustedPrimaryColor() = if (isBlackAndWhiteTheme()) Color.WHITE else baseConfig.primaryColor
+
+fun Context.toast(id: Int, length: Int = Toast.LENGTH_SHORT) {
+ toast(getString(id), length)
+}
+
+fun Context.toast(msg: String, length: Int = Toast.LENGTH_SHORT) {
+ try {
+ Toast.makeText(applicationContext, msg, length).show()
+ } catch (e: Exception) {
+ }
+}
+
+val Context.baseConfig: BaseConfig get() = BaseConfig.newInstance(this)
+val Context.sdCardPath: String get() = baseConfig.sdCardPath
+val Context.internalStoragePath: String get() = baseConfig.internalStoragePath
+
+@SuppressLint("InlinedApi", "NewApi")
+fun Context.isFingerPrintSensorAvailable() = isMarshmallowPlus()
+
+fun Context.getLatestMediaId(uri: Uri = MediaStore.Files.getContentUri("external")): Long {
+ val MAX_VALUE = "max_value"
+ val projection = arrayOf("MAX(${BaseColumns._ID}) AS $MAX_VALUE")
+ var cursor: Cursor? = null
+ try {
+ cursor = contentResolver.query(uri, projection, null, null, null)
+ if (cursor?.moveToFirst() == true) {
+ return cursor.getLongValue(MAX_VALUE)
+ }
+ } finally {
+ cursor?.close()
+ }
+ return 0
+}
+
+fun Context.getLatestMediaByDateId(uri: Uri = MediaStore.Files.getContentUri("external")): Long {
+ val projection = arrayOf(BaseColumns._ID)
+ val sortOrder = "${MediaStore.Images.ImageColumns.DATE_TAKEN} DESC"
+ var cursor: Cursor? = null
+ try {
+ cursor = contentResolver.query(uri, projection, null, null, sortOrder)
+ if (cursor?.moveToFirst() == true) {
+ return cursor.getLongValue(BaseColumns._ID)
+ }
+ } finally {
+ cursor?.close()
+ }
+ return 0
+}
+
+// some helper functions were taken from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java
+@SuppressLint("NewApi")
+fun Context.getRealPathFromURI(uri: Uri): String? {
+ if (uri.scheme == "file") {
+ return uri.path
+ }
+
+ if (isKitkatPlus()) {
+ if (isDownloadsDocument(uri)) {
+ val id = DocumentsContract.getDocumentId(uri)
+ if (id.areDigitsOnly()) {
+ val newUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), id.toLong())
+ val path = getDataColumn(newUri)
+ if (path != null) {
+ return path
+ }
+ }
+ } else if (isExternalStorageDocument(uri)) {
+ val documentId = DocumentsContract.getDocumentId(uri)
+ val parts = documentId.split(":")
+ if (parts[0].equals("primary", true)) {
+ return "${Environment.getExternalStorageDirectory().absolutePath}/${parts[1]}"
+ }
+ } else if (isMediaDocument(uri)) {
+ val documentId = DocumentsContract.getDocumentId(uri)
+ val split = documentId.split(":").dropLastWhile { it.isEmpty() }.toTypedArray()
+ val type = split[0]
+
+ val contentUri = when (type) {
+ "video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ "audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
+ else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ }
+
+ val selection = "_id=?"
+ val selectionArgs = arrayOf(split[1])
+ val path = getDataColumn(contentUri, selection, selectionArgs)
+ if (path != null) {
+ return path
+ }
+ }
+ }
+
+ return getDataColumn(uri)
+}
+
+fun Context.getDataColumn(uri: Uri, selection: String? = null, selectionArgs: Array? = null): String? {
+ var cursor: Cursor? = null
+ try {
+ val projection = arrayOf(MediaStore.Files.FileColumns.DATA)
+ cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)
+ if (cursor?.moveToFirst() == true) {
+ return cursor.getStringValue(MediaStore.Files.FileColumns.DATA)
+ }
+ } catch (e: Exception) {
+ } finally {
+ cursor?.close()
+ }
+ return null
+}
+
+private fun isMediaDocument(uri: Uri) = uri.authority == "com.android.providers.media.documents"
+
+private fun isDownloadsDocument(uri: Uri) = uri.authority == "com.android.providers.downloads.documents"
+
+private fun isExternalStorageDocument(uri: Uri) = uri.authority == "com.android.externalstorage.documents"
+
+fun Context.hasPermission(permId: Int) = ContextCompat.checkSelfPermission(this, getPermissionString(permId)) == PackageManager.PERMISSION_GRANTED
+
+fun Context.getPermissionString(id: Int) = when (id) {
+ PERMISSION_READ_STORAGE -> Manifest.permission.READ_EXTERNAL_STORAGE
+ PERMISSION_WRITE_STORAGE -> Manifest.permission.WRITE_EXTERNAL_STORAGE
+ PERMISSION_CAMERA -> Manifest.permission.CAMERA
+ PERMISSION_RECORD_AUDIO -> Manifest.permission.RECORD_AUDIO
+ PERMISSION_READ_CONTACTS -> Manifest.permission.READ_CONTACTS
+ PERMISSION_WRITE_CONTACTS -> Manifest.permission.WRITE_CONTACTS
+ PERMISSION_READ_CALENDAR -> Manifest.permission.READ_CALENDAR
+ PERMISSION_WRITE_CALENDAR -> Manifest.permission.WRITE_CALENDAR
+ PERMISSION_CALL_PHONE -> Manifest.permission.CALL_PHONE
+ PERMISSION_READ_CALL_LOG -> Manifest.permission.READ_CALL_LOG
+ PERMISSION_WRITE_CALL_LOG -> Manifest.permission.WRITE_CALL_LOG
+ PERMISSION_GET_ACCOUNTS -> Manifest.permission.GET_ACCOUNTS
+ else -> ""
+}
+
+fun Context.getFilePublicUri(file: File, applicationId: String): Uri {
+ // for images/videos/gifs try getting a media content uri first, like content://media/external/images/media/438
+ // if media content uri is null, get our custom uri like content://com.simplemobiletools.gallery.provider/external_files/emulated/0/DCIM/IMG_20171104_233915.jpg
+ return if (file.isMediaFile()) {
+ getMediaContentUri(file.absolutePath) ?: FileProvider.getUriForFile(this, "$applicationId.provider", file)
+ } else {
+ FileProvider.getUriForFile(this, "$applicationId.provider", file)
+ }
+}
+
+fun Context.getMediaContentUri(path: String): Uri? {
+ val uri = when {
+ path.isImageFast() -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
+ path.isVideoFast() -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ else -> MediaStore.Files.getContentUri("external")
+ }
+
+ return getMediaContent(path, uri)
+}
+
+fun Context.getMediaContent(path: String, uri: Uri): Uri? {
+ val projection = arrayOf(MediaStore.Images.Media._ID)
+ val selection = MediaStore.Images.Media.DATA + "= ?"
+ val selectionArgs = arrayOf(path)
+ var cursor: Cursor? = null
+ try {
+ cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)
+ if (cursor?.moveToFirst() == true) {
+ val id = cursor.getIntValue(MediaStore.Images.Media._ID).toString()
+ return Uri.withAppendedPath(uri, id)
+ }
+ } catch (e: Exception) {
+ } finally {
+ cursor?.close()
+ }
+ return null
+}
+
+fun Context.getFilenameFromUri(uri: Uri): String {
+ return if (uri.scheme == "file") {
+ File(uri.toString()).name
+ } else {
+ var name = getFilenameFromContentUri(uri) ?: ""
+ if (name.isEmpty()) {
+ name = uri.lastPathSegment ?: ""
+ }
+ name
+ }
+}
+
+fun Context.getMimeTypeFromUri(uri: Uri): String {
+ var mimetype = uri.path.getMimeType()
+ if (mimetype.isEmpty()) {
+ try {
+ mimetype = contentResolver.getType(uri)
+ } catch (e: IllegalStateException) {
+ }
+ }
+ return mimetype
+}
+
+fun Context.ensurePublicUri(path: String, applicationId: String): Uri? {
+ return if (path.startsWith(OTG_PATH)) {
+ getDocumentFile(path)?.uri
+ } else {
+ val uri = Uri.parse(path)
+ if (uri.scheme == "content") {
+ uri
+ } else {
+ val newPath = if (uri.toString().startsWith("/")) uri.toString() else uri.path
+ val file = File(newPath)
+ getFilePublicUri(file, applicationId)
+ }
+ }
+}
+
+fun Context.ensurePublicUri(uri: Uri, applicationId: String): Uri {
+ return if (uri.scheme == "content") {
+ uri
+ } else {
+ val file = File(uri.path)
+ getFilePublicUri(file, applicationId)
+ }
+}
+
+fun Context.getFilenameFromContentUri(uri: Uri): String? {
+ var cursor: Cursor? = null
+ try {
+ cursor = contentResolver.query(uri, null, null, null, null)
+ if (cursor?.moveToFirst() == true) {
+ return cursor.getStringValue(OpenableColumns.DISPLAY_NAME)
+ }
+ } catch (e: Exception) {
+ } finally {
+ cursor?.close()
+ }
+ return ""
+}
+
+fun Context.getSharedTheme(callback: (sharedTheme: SharedTheme?) -> Unit) {
+ if (!isThankYouInstalled()) {
+ callback(null)
+ } else {
+ val cursorLoader = getMyContentProviderCursorLoader()
+ Thread {
+ callback(getSharedThemeSync(cursorLoader))
+ }.start()
+ }
+}
+
+fun Context.getSharedThemeSync(cursorLoader: CursorLoader): SharedTheme? {
+ val cursor = cursorLoader.loadInBackground()
+ cursor?.use {
+ if (cursor.moveToFirst()) {
+ val textColor = cursor.getIntValue(COL_TEXT_COLOR)
+ val backgroundColor = cursor.getIntValue(COL_BACKGROUND_COLOR)
+ val primaryColor = cursor.getIntValue(COL_PRIMARY_COLOR)
+ val appIconColor = cursor.getIntValue(COL_APP_ICON_COLOR)
+ val lastUpdatedTS = cursor.getIntValue(COL_LAST_UPDATED_TS)
+ return SharedTheme(textColor, backgroundColor, primaryColor, appIconColor, lastUpdatedTS)
+ }
+ }
+ return null
+}
+
+fun Context.getMyContentProviderCursorLoader() = CursorLoader(this, MyContentProvider.MY_CONTENT_URI, null, null, null, null)
+
+fun Context.getDialogTheme() = if (baseConfig.backgroundColor.getContrastColor() == Color.WHITE) R.style.MyDialogTheme_Dark else R.style.MyDialogTheme
+
+fun Context.getCurrentFormattedDateTime(): String {
+ val simpleDateFormat = SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.getDefault())
+ return simpleDateFormat.format(Date(System.currentTimeMillis()))
+}
+
+fun Context.updateSDCardPath() {
+ Thread {
+ val oldPath = baseConfig.sdCardPath
+ baseConfig.sdCardPath = getSDCardPath()
+ if (oldPath != baseConfig.sdCardPath) {
+ baseConfig.treeUri = ""
+ }
+ }.start()
+}
+
+fun Context.getUriMimeType(path: String, newUri: Uri): String {
+ var mimeType = path.getMimeType()
+ if (mimeType.isEmpty()) {
+ mimeType = getMimeTypeFromUri(newUri)
+ }
+ return mimeType
+}
+
+fun Context.isThankYouInstalled() = isPackageInstalled("com.simplemobiletools.thankyou")
+
+fun Context.isPackageInstalled(pkgName: String): Boolean {
+ return try {
+ packageManager.getPackageInfo(pkgName, 0)
+ true
+ } catch (e: Exception) {
+ false
+ }
+}
+
+// format day bits to strings like "Mon, Tue, Wed"
+fun Context.getSelectedDaysString(bitMask: Int): String {
+ val dayBits = arrayListOf(MONDAY_BIT, TUESDAY_BIT, WEDNESDAY_BIT, THURSDAY_BIT, FRIDAY_BIT, SATURDAY_BIT, SUNDAY_BIT)
+ val weekDays = resources.getStringArray(R.array.week_days_short).toList() as ArrayList
+
+ if (baseConfig.isSundayFirst) {
+ dayBits.moveLastItemToFront()
+ weekDays.moveLastItemToFront()
+ }
+
+ var days = ""
+ dayBits.forEachIndexed { index, bit ->
+ if (bitMask and bit != 0) {
+ days += "${weekDays[index]}, "
+ }
+ }
+ return days.trim().trimEnd(',')
+}
+
+fun Context.formatMinutesToTimeString(totalMinutes: Int) = formatSecondsToTimeString(totalMinutes * 60)
+
+fun Context.formatSecondsToTimeString(totalSeconds: Int): String {
+ val days = totalSeconds / DAY_SECONDS
+ val hours = (totalSeconds % DAY_SECONDS) / HOUR_SECONDS
+ val minutes = (totalSeconds % HOUR_SECONDS) / MINUTE_SECONDS
+ val seconds = totalSeconds % MINUTE_SECONDS
+ val timesString = StringBuilder()
+ if (days > 0) {
+ val daysString = String.format(resources.getQuantityString(R.plurals.days, days, days))
+ timesString.append("$daysString, ")
+ }
+
+ if (hours > 0) {
+ val hoursString = String.format(resources.getQuantityString(R.plurals.hours, hours, hours))
+ timesString.append("$hoursString, ")
+ }
+
+ if (minutes > 0) {
+ val minutesString = String.format(resources.getQuantityString(R.plurals.minutes, minutes, minutes))
+ timesString.append("$minutesString, ")
+ }
+
+ if (seconds > 0) {
+ val secondsString = String.format(resources.getQuantityString(R.plurals.seconds, seconds, seconds))
+ timesString.append(secondsString)
+ }
+
+ var result = timesString.toString().trim().trimEnd(',')
+ if (result.isEmpty()) {
+ result = String.format(resources.getQuantityString(R.plurals.minutes, 0, 0))
+ }
+ return result
+}
+
+fun Context.getFormattedMinutes(minutes: Int, showBefore: Boolean = true) = getFormattedSeconds(if (minutes <= 0) minutes else minutes * 60, showBefore)
+
+fun Context.getFormattedSeconds(seconds: Int, showBefore: Boolean = true) = when (seconds) {
+ -1 -> getString(R.string.no_reminder)
+ 0 -> getString(R.string.at_start)
+ else -> {
+ if (seconds % YEAR_SECONDS == 0)
+ resources.getQuantityString(R.plurals.years, seconds / YEAR_SECONDS, seconds / YEAR_SECONDS)
+
+ when {
+ seconds % MONTH_SECONDS == 0 -> resources.getQuantityString(R.plurals.months, seconds / MONTH_SECONDS, seconds / MONTH_SECONDS)
+ seconds % WEEK_SECONDS == 0 -> resources.getQuantityString(R.plurals.weeks, seconds / WEEK_SECONDS, seconds / WEEK_SECONDS)
+ seconds % DAY_SECONDS == 0 -> resources.getQuantityString(R.plurals.days, seconds / DAY_SECONDS, seconds / DAY_SECONDS)
+ seconds % HOUR_SECONDS == 0 -> {
+ val base = if (showBefore) R.plurals.hours_before else R.plurals.by_hours
+ resources.getQuantityString(base, seconds / HOUR_SECONDS, seconds / HOUR_SECONDS)
+ }
+ seconds % MINUTE_SECONDS == 0 -> {
+ val base = if (showBefore) R.plurals.minutes_before else R.plurals.by_minutes
+ resources.getQuantityString(base, seconds / MINUTE_SECONDS, seconds / MINUTE_SECONDS)
+ }
+ else -> {
+ val base = if (showBefore) R.plurals.seconds_before else R.plurals.by_seconds
+ resources.getQuantityString(base, seconds, seconds)
+ }
+ }
+ }
+}
+
+fun Context.getDefaultAlarmUri(type: Int) = RingtoneManager.getDefaultUri(if (type == ALARM_SOUND_TYPE_NOTIFICATION) RingtoneManager.TYPE_NOTIFICATION else RingtoneManager.TYPE_ALARM)
+
+fun Context.getDefaultAlarmTitle(type: Int): String {
+ val alarmString = getString(R.string.alarm)
+ return try {
+ RingtoneManager.getRingtone(this, getDefaultAlarmUri(type))?.getTitle(this) ?: alarmString
+ } catch (e: Exception) {
+ alarmString
+ }
+}
+
+fun Context.getDefaultAlarmSound(type: Int) = AlarmSound(0, getDefaultAlarmTitle(type), getDefaultAlarmUri(type).toString())
+
+fun Context.grantReadUriPermission(uriString: String) {
+ try {
+ // ensure custom reminder sounds play well
+ grantUriPermission("com.android.systemui", Uri.parse(uriString), Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ } catch (ignored: Exception) {
+ }
+}
+
+fun Context.storeNewYourAlarmSound(resultData: Intent): AlarmSound {
+ val uri = resultData.data
+ var filename = getFilenameFromUri(uri)
+ if (filename.isEmpty()) {
+ filename = getString(R.string.alarm)
+ }
+
+ val token = object : TypeToken>() {}.type
+ val yourAlarmSounds = Gson().fromJson>(baseConfig.yourAlarmSounds, token) ?: ArrayList()
+ val newAlarmSoundId = (yourAlarmSounds.maxBy { it.id }?.id ?: YOUR_ALARM_SOUNDS_MIN_ID) + 1
+ val newAlarmSound = AlarmSound(newAlarmSoundId, filename, uri.toString())
+ if (yourAlarmSounds.firstOrNull { it.uri == uri.toString() } == null) {
+ yourAlarmSounds.add(newAlarmSound)
+ }
+
+ baseConfig.yourAlarmSounds = Gson().toJson(yourAlarmSounds)
+
+ if (isKitkatPlus()) {
+ val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ contentResolver.takePersistableUriPermission(uri, takeFlags)
+ }
+
+ return newAlarmSound
+}
+
+@RequiresApi(Build.VERSION_CODES.N)
+fun Context.saveImageRotation(path: String, degrees: Int): Boolean {
+ if (!isPathOnSD(path) && !path.startsWith(OTG_PATH)) {
+ saveExifRotation(ExifInterface(path), degrees)
+ return true
+ } else if (isNougatPlus()) {
+ val documentFile = getSomeDocumentFile(path)
+ if (documentFile != null) {
+ val parcelFileDescriptor = contentResolver.openFileDescriptor(documentFile.uri, "rw")
+ val fileDescriptor = parcelFileDescriptor.fileDescriptor
+ saveExifRotation(ExifInterface(fileDescriptor), degrees)
+ return true
+ }
+ }
+ return false
+}
+
+fun Context.saveExifRotation(exif: ExifInterface, degrees: Int) {
+ val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
+ val orientationDegrees = (orientation.degreesFromOrientation() + degrees) % 360
+ exif.setAttribute(ExifInterface.TAG_ORIENTATION, orientationDegrees.orientationFromDegrees())
+ exif.saveAttributes()
+}
+
+fun Context.checkAppIconColor() {
+ val appId = baseConfig.appId
+ if (appId.isNotEmpty() && baseConfig.lastIconColor != baseConfig.appIconColor) {
+ getAppIconColors().forEachIndexed { index, color ->
+ toggleAppIconColor(appId, index, color, false)
+ }
+
+ getAppIconColors().forEachIndexed { index, color ->
+ if (baseConfig.appIconColor == color) {
+ toggleAppIconColor(appId, index, color, true)
+ }
+ }
+ }
+}
+
+fun Context.toggleAppIconColor(appId: String, colorIndex: Int, color: Int, enable: Boolean) {
+ val className = "${appId.removeSuffix(".debug")}.activities.SplashActivity${appIconColorStrings[colorIndex]}"
+ val state = if (enable) PackageManager.COMPONENT_ENABLED_STATE_ENABLED else PackageManager.COMPONENT_ENABLED_STATE_DISABLED
+ try {
+ packageManager.setComponentEnabledSetting(ComponentName(appId, className), state, PackageManager.DONT_KILL_APP)
+ if (enable) {
+ baseConfig.lastIconColor = color
+ }
+ } catch (e: Exception) {
+ }
+}
+
+fun Context.getAppIconColors() = resources.getIntArray(R.array.md_app_icon_colors).toCollection(ArrayList())
+
+fun Context.getLaunchIntent() = packageManager.getLaunchIntentForPackage(baseConfig.appId)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Cursor.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Cursor.kt
new file mode 100644
index 0000000..8af687b
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Cursor.kt
@@ -0,0 +1,11 @@
+package com.simplemobiletools.commons.extensions
+
+import android.database.Cursor
+
+fun Cursor.getStringValue(key: String) = getString(getColumnIndex(key))
+
+fun Cursor.getIntValue(key: String) = getInt(getColumnIndex(key))
+
+fun Cursor.getLongValue(key: String) = getLong(getColumnIndex(key))
+
+fun Cursor.getBlobValue(key: String) = getBlob(getColumnIndex(key))
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/DocumentFile.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/DocumentFile.kt
new file mode 100644
index 0000000..41a6543
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/DocumentFile.kt
@@ -0,0 +1,56 @@
+package com.simplemobiletools.commons.extensions
+
+import android.support.v4.provider.DocumentFile
+
+fun DocumentFile.getItemSize(countHiddenItems: Boolean): Long {
+ return if (isDirectory) {
+ getDirectorySize(this, countHiddenItems)
+ } else {
+ length()
+ }
+}
+
+private fun getDirectorySize(dir: DocumentFile, countHiddenItems: Boolean): Long {
+ var size = 0L
+ if (dir.exists()) {
+ val files = dir.listFiles()
+ if (files != null) {
+ for (i in files.indices) {
+ val file = files[i]
+ if (file.isDirectory) {
+ size += getDirectorySize(file, countHiddenItems)
+ } else if (!file.name!!.startsWith(".") || countHiddenItems) {
+ size += file.length()
+ }
+ }
+ }
+ }
+ return size
+}
+
+fun DocumentFile.getFileCount(countHiddenItems: Boolean): Int {
+ return if (isDirectory) {
+ getDirectoryFileCount(this, countHiddenItems)
+ } else {
+ 1
+ }
+}
+
+private fun getDirectoryFileCount(dir: DocumentFile, countHiddenItems: Boolean): Int {
+ var count = 0
+ if (dir.exists()) {
+ val files = dir.listFiles()
+ if (files != null) {
+ for (i in files.indices) {
+ val file = files[i]
+ if (file.isDirectory) {
+ count++
+ count += getDirectoryFileCount(file, countHiddenItems)
+ } else if (!file.name!!.startsWith(".") || countHiddenItems) {
+ count++
+ }
+ }
+ }
+ }
+ return count
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Drawable.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Drawable.kt
new file mode 100644
index 0000000..6cfc560
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Drawable.kt
@@ -0,0 +1,6 @@
+package com.simplemobiletools.commons.extensions
+
+import android.graphics.PorterDuff
+import android.graphics.drawable.Drawable
+
+fun Drawable.applyColorFilter(color: Int) = mutate().setColorFilter(color, PorterDuff.Mode.SRC_IN)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/EditText.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/EditText.kt
new file mode 100644
index 0000000..d483d31
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/EditText.kt
@@ -0,0 +1,5 @@
+package com.simplemobiletools.commons.extensions
+
+import android.widget.EditText
+
+val EditText.value: String get() = text.toString().trim()
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ExifInterface.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ExifInterface.kt
new file mode 100644
index 0000000..8178ae2
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ExifInterface.kt
@@ -0,0 +1,43 @@
+package com.simplemobiletools.commons.extensions
+
+import android.media.ExifInterface
+import java.io.IOException
+
+fun ExifInterface.copyTo(destination: ExifInterface) {
+ val attributes = arrayOf(
+ ExifInterface.TAG_APERTURE,
+ ExifInterface.TAG_DATETIME,
+ ExifInterface.TAG_DATETIME_DIGITIZED,
+ ExifInterface.TAG_DATETIME_ORIGINAL,
+ ExifInterface.TAG_EXPOSURE_TIME,
+ ExifInterface.TAG_FLASH,
+ ExifInterface.TAG_FOCAL_LENGTH,
+ ExifInterface.TAG_GPS_ALTITUDE,
+ ExifInterface.TAG_GPS_ALTITUDE_REF,
+ ExifInterface.TAG_GPS_DATESTAMP,
+ ExifInterface.TAG_GPS_LATITUDE,
+ ExifInterface.TAG_GPS_LATITUDE_REF,
+ ExifInterface.TAG_GPS_LONGITUDE,
+ ExifInterface.TAG_GPS_LONGITUDE_REF,
+ ExifInterface.TAG_GPS_PROCESSING_METHOD,
+ ExifInterface.TAG_GPS_TIMESTAMP,
+ ExifInterface.TAG_IMAGE_LENGTH,
+ ExifInterface.TAG_IMAGE_WIDTH,
+ ExifInterface.TAG_ISO_SPEED_RATINGS,
+ ExifInterface.TAG_MAKE,
+ ExifInterface.TAG_MODEL,
+ ExifInterface.TAG_ORIENTATION,
+ ExifInterface.TAG_WHITE_BALANCE)
+
+ attributes.forEach {
+ val value = getAttribute(it)
+ if (value != null) {
+ destination.setAttribute(it, value)
+ }
+ }
+
+ try {
+ destination.saveAttributes()
+ } catch (ignored: IOException) {
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/File.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/File.kt
new file mode 100644
index 0000000..14ebf3b
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/File.kt
@@ -0,0 +1,78 @@
+package com.simplemobiletools.commons.extensions
+
+import android.content.Context
+import com.simplemobiletools.commons.helpers.audioExtensions
+import com.simplemobiletools.commons.helpers.photoExtensions
+import com.simplemobiletools.commons.helpers.rawExtensions
+import com.simplemobiletools.commons.helpers.videoExtensions
+import com.simplemobiletools.commons.models.FileDirItem
+import java.io.File
+
+fun File.isMediaFile() = absolutePath.isImageFast() || absolutePath.isVideoFast() || absolutePath.isGif() || absolutePath.isRawFast() || absolutePath.isSvg()
+fun File.isGif() = absolutePath.endsWith(".gif", true)
+fun File.isVideoFast() = videoExtensions.any { absolutePath.endsWith(it, true) }
+fun File.isImageFast() = photoExtensions.any { absolutePath.endsWith(it, true) }
+fun File.isAudioFast() = audioExtensions.any { absolutePath.endsWith(it, true) }
+fun File.isRawFast() = rawExtensions.any { absolutePath.endsWith(it, true) }
+
+fun File.isImageSlow() = absolutePath.isImageFast() || getMimeType().startsWith("image")
+fun File.isVideoSlow() = absolutePath.isVideoFast() || getMimeType().startsWith("video")
+fun File.isAudioSlow() = absolutePath.isAudioFast() || getMimeType().startsWith("audio")
+
+fun File.getMimeType() = absolutePath.getMimeType()
+
+fun File.getProperSize(countHiddenItems: Boolean): Long {
+ return if (isDirectory) {
+ getDirectorySize(this, countHiddenItems)
+ } else {
+ length()
+ }
+}
+
+private fun getDirectorySize(dir: File, countHiddenItems: Boolean): Long {
+ var size = 0L
+ if (dir.exists()) {
+ val files = dir.listFiles()
+ if (files != null) {
+ for (i in files.indices) {
+ if (files[i].isDirectory) {
+ size += getDirectorySize(files[i], countHiddenItems)
+ } else if (!files[i].isHidden && !dir.isHidden || countHiddenItems) {
+ size += files[i].length()
+ }
+ }
+ }
+ }
+ return size
+}
+
+fun File.getFileCount(countHiddenItems: Boolean): Int {
+ return if (isDirectory) {
+ getDirectoryFileCount(this, countHiddenItems)
+ } else {
+ 1
+ }
+}
+
+private fun getDirectoryFileCount(dir: File, countHiddenItems: Boolean): Int {
+ var count = 0
+ if (dir.exists()) {
+ val files = dir.listFiles()
+ if (files != null) {
+ for (i in files.indices) {
+ val file = files[i]
+ if (file.isDirectory) {
+ count++
+ count += getDirectoryFileCount(file, countHiddenItems)
+ } else if (!file.isHidden || countHiddenItems) {
+ count++
+ }
+ }
+ }
+ }
+ return count
+}
+
+fun File.getDirectChildrenCount(countHiddenItems: Boolean) = listFiles()?.filter { if (countHiddenItems) true else !it.isHidden }?.size ?: 0
+
+fun File.toFileDirItem(context: Context) = FileDirItem(absolutePath, name, context.getIsPathDirectory(absolutePath), 0, length())
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ImageView.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ImageView.kt
new file mode 100644
index 0000000..2c18df4
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ImageView.kt
@@ -0,0 +1,17 @@
+package com.simplemobiletools.commons.extensions
+
+import android.graphics.PorterDuff
+import android.graphics.drawable.GradientDrawable
+import android.widget.ImageView
+
+fun ImageView.setFillWithStroke(fillColor: Int, backgroundColor: Int) {
+ val strokeColor = backgroundColor.getContrastColor()
+ GradientDrawable().apply {
+ shape = GradientDrawable.RECTANGLE
+ setColor(fillColor)
+ setStroke(2, strokeColor)
+ setBackgroundDrawable(this)
+ }
+}
+
+fun ImageView.applyColorFilter(color: Int) = setColorFilter(color, PorterDuff.Mode.SRC_IN)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Int.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Int.kt
new file mode 100644
index 0000000..5d8ce9d
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Int.kt
@@ -0,0 +1,99 @@
+package com.simplemobiletools.commons.extensions
+
+import android.graphics.Color
+import android.media.ExifInterface
+import java.util.*
+
+fun Int.getContrastColor(): Int {
+ val DARK_GREY = -13421773
+ val y = (299 * Color.red(this) + 587 * Color.green(this) + 114 * Color.blue(this)) / 1000
+ return if (y >= 149) DARK_GREY else Color.WHITE
+}
+
+fun Int.toHex() = String.format("#%06X", 0xFFFFFF and this).toUpperCase()
+
+fun Int.adjustAlpha(factor: Float): Int {
+ val alpha = Math.round(Color.alpha(this) * factor)
+ val red = Color.red(this)
+ val green = Color.green(this)
+ val blue = Color.blue(this)
+ return Color.argb(alpha, red, green, blue)
+}
+
+fun Int.getFormattedDuration(): String {
+ val sb = StringBuilder(8)
+ val hours = this / 3600
+ val minutes = this % 3600 / 60
+ val seconds = this % 60
+
+ if (this >= 3600) {
+ sb.append(String.format(Locale.getDefault(), "%02d", hours)).append(":")
+ }
+
+ sb.append(String.format(Locale.getDefault(), "%02d", minutes))
+ sb.append(":").append(String.format(Locale.getDefault(), "%02d", seconds))
+ return sb.toString()
+}
+
+// TODO: how to do "bits & ~bit" in kotlin?
+fun Int.removeBit(bit: Int) = addBit(bit) - bit
+
+fun Int.addBit(bit: Int) = this or bit
+
+fun Int.flipBit(bit: Int) = if (this and bit == 0) addBit(bit) else removeBit(bit)
+
+fun ClosedRange.random() = Random().nextInt(endInclusive - start) + start
+
+// taken from https://stackoverflow.com/a/40964456/1967672
+fun Int.darkenColor(): Int {
+ if (this == Color.WHITE) {
+ return -2105377
+ } else if (this == Color.BLACK) {
+ return Color.BLACK
+ }
+
+ val DARK_FACTOR = 8
+ var hsv = FloatArray(3)
+ Color.colorToHSV(this, hsv)
+ val hsl = hsv2hsl(hsv)
+ hsl[2] -= DARK_FACTOR / 100f
+ if (hsl[2] < 0)
+ hsl[2] = 0f
+ hsv = hsl2hsv(hsl)
+ return Color.HSVToColor(hsv)
+}
+
+private fun hsl2hsv(hsl: FloatArray): FloatArray {
+ val hue = hsl[0]
+ var sat = hsl[1]
+ val light = hsl[2]
+ sat *= if (light < .5) light else 1 - light
+ return floatArrayOf(hue, 2f * sat / (light + sat), light + sat)
+}
+
+private fun hsv2hsl(hsv: FloatArray): FloatArray {
+ val hue = hsv[0]
+ val sat = hsv[1]
+ val value = hsv[2]
+
+ val newHue = (2f - sat) * value
+ var newSat = sat * value / if (newHue < 1f) newHue else 2f - newHue
+ if (newSat > 1f)
+ newSat = 1f
+
+ return floatArrayOf(hue, newSat, newHue / 2f)
+}
+
+fun Int.orientationFromDegrees() = when (this) {
+ 270 -> ExifInterface.ORIENTATION_ROTATE_270
+ 180 -> ExifInterface.ORIENTATION_ROTATE_180
+ 90 -> ExifInterface.ORIENTATION_ROTATE_90
+ else -> ExifInterface.ORIENTATION_NORMAL
+}.toString()
+
+fun Int.degreesFromOrientation() = when (this) {
+ ExifInterface.ORIENTATION_ROTATE_270 -> 270
+ ExifInterface.ORIENTATION_ROTATE_180 -> 180
+ ExifInterface.ORIENTATION_ROTATE_90 -> 90
+ else -> 0
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/List.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/List.kt
new file mode 100644
index 0000000..8b18b14
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/List.kt
@@ -0,0 +1,23 @@
+package com.simplemobiletools.commons.extensions
+
+import java.util.*
+
+fun List.getMimeType(): String {
+ val mimeGroups = HashSet(size)
+ val subtypes = HashSet(size)
+ forEach {
+ val parts = it.getMimeType().split("/")
+ if (parts.size == 2) {
+ mimeGroups.add(parts.getOrElse(0, { "" }))
+ subtypes.add(parts.getOrElse(1, { "" }))
+ } else {
+ return "*/*"
+ }
+ }
+
+ return when {
+ subtypes.size == 1 -> "${mimeGroups.first()}/${subtypes.first()}"
+ mimeGroups.size == 1 -> "${mimeGroups.first()}/*"
+ else -> "*/*"
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Long.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Long.kt
new file mode 100644
index 0000000..993ad70
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Long.kt
@@ -0,0 +1,20 @@
+package com.simplemobiletools.commons.extensions
+
+import android.text.format.DateFormat
+import java.text.DecimalFormat
+import java.util.*
+
+fun Long.formatSize(): String {
+ if (this <= 0)
+ return "0 B"
+
+ val units = arrayOf("B", "kB", "MB", "GB", "TB")
+ val digitGroups = (Math.log10(toDouble()) / Math.log10(1024.0)).toInt()
+ return "${DecimalFormat("#,##0.#").format(this / Math.pow(1024.0, digitGroups.toDouble()))} ${units[digitGroups]}"
+}
+
+fun Long.formatDate(): String {
+ val cal = Calendar.getInstance(Locale.ENGLISH)
+ cal.timeInMillis = this
+ return DateFormat.format("dd.MM.yyyy kk:mm", cal).toString()
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Point.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Point.kt
new file mode 100644
index 0000000..0d54ac4
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Point.kt
@@ -0,0 +1,11 @@
+package com.simplemobiletools.commons.extensions
+
+import android.graphics.Point
+
+fun Point.formatAsResolution() = "$x x $y ${getMPx()}"
+
+fun Point.getMPx(): String {
+ val px = x * y / 1000000.toFloat()
+ val rounded = Math.round(px * 10) / 10.toFloat()
+ return "(${rounded}MP)"
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/RemoteViews.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/RemoteViews.kt
new file mode 100644
index 0000000..3ec486b
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/RemoteViews.kt
@@ -0,0 +1,20 @@
+package com.simplemobiletools.commons.extensions
+
+import android.view.View
+import android.widget.RemoteViews
+
+fun RemoteViews.setBackgroundColor(id: Int, color: Int) {
+ setInt(id, "setBackgroundColor", color)
+}
+
+fun RemoteViews.setTextSize(id: Int, size: Float) {
+ setFloat(id, "setTextSize", size)
+}
+
+fun RemoteViews.setText(id: Int, text: String) {
+ setTextViewText(id, text)
+}
+
+fun RemoteViews.setVisibleIf(id: Int, beVisible: Boolean) {
+ setViewVisibility(id, if (beVisible) View.VISIBLE else View.GONE)
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Resources.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Resources.kt
new file mode 100644
index 0000000..3498b58
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/Resources.kt
@@ -0,0 +1,39 @@
+package com.simplemobiletools.commons.extensions
+
+import android.content.res.Resources
+import android.graphics.*
+import android.graphics.drawable.Drawable
+
+fun Resources.getColoredBitmap(resourceId: Int, newColor: Int): Bitmap {
+ val options = BitmapFactory.Options()
+ options.inMutable = true
+ val bmp = BitmapFactory.decodeResource(this, resourceId, options)
+ val paint = Paint()
+ val filter = PorterDuffColorFilter(newColor, PorterDuff.Mode.SRC_IN)
+ paint.colorFilter = filter
+ val canvas = Canvas(bmp)
+ canvas.drawBitmap(bmp, 0f, 0f, paint)
+ return bmp
+}
+
+fun Resources.getColoredDrawable(drawableId: Int, colorId: Int, alpha: Int = 255) = getColoredDrawableWithColor(drawableId, getColor(colorId), alpha)
+
+fun Resources.getColoredDrawableWithColor(drawableId: Int, color: Int, alpha: Int = 255): Drawable {
+ val drawable = getDrawable(drawableId)
+ drawable.mutate().applyColorFilter(color)
+ drawable.mutate().alpha = alpha
+ return drawable
+}
+
+fun Resources.hasNavBar(): Boolean {
+ val id = getIdentifier("config_showNavigationBar", "bool", "android")
+ return id > 0 && getBoolean(id)
+}
+
+fun Resources.getNavBarHeight(): Int {
+ val id = getIdentifier("navigation_bar_height", "dimen", "android")
+ return if (id > 0 && hasNavBar()) {
+ getDimensionPixelSize(id)
+ } else
+ 0
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/String.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/String.kt
new file mode 100644
index 0000000..c9097c2
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/String.kt
@@ -0,0 +1,870 @@
+package com.simplemobiletools.commons.extensions
+
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Point
+import android.media.ExifInterface
+import android.media.MediaMetadataRetriever
+import android.text.Spannable
+import android.text.SpannableString
+import android.text.style.ForegroundColorSpan
+import com.simplemobiletools.commons.helpers.*
+import java.text.Normalizer
+import java.text.SimpleDateFormat
+import java.util.*
+
+fun String.getFilenameFromPath() = substring(lastIndexOf("/") + 1)
+
+fun String.getFilenameExtension() = substring(lastIndexOf(".") + 1)
+
+fun String.getBasePath(context: Context): String {
+ return if (startsWith(context.internalStoragePath)) {
+ context.internalStoragePath
+ } else if (!context.sdCardPath.isEmpty() && startsWith(context.sdCardPath)) {
+ context.sdCardPath
+ } else if (startsWith(OTG_PATH)) {
+ OTG_PATH
+ } else {
+ "/"
+ }
+}
+
+fun String.isAValidFilename(): Boolean {
+ val ILLEGAL_CHARACTERS = charArrayOf('/', '\n', '\r', '\t', '\u0000', '`', '?', '*', '\\', '<', '>', '|', '\"', ':')
+ ILLEGAL_CHARACTERS.forEach {
+ if (contains(it))
+ return false
+ }
+ return true
+}
+
+fun String.isMediaFile() = isImageFast() || isVideoFast() || isGif() || isRawFast() || isSvg()
+
+fun String.isGif() = endsWith(".gif", true)
+
+fun String.isPng() = endsWith(".png", true)
+
+fun String.isJpg() = endsWith(".jpg", true) or endsWith(".jpeg")
+
+fun String.isSvg() = endsWith(".svg", true)
+
+// fast extension checks, not guaranteed to be accurate
+fun String.isVideoFast() = videoExtensions.any { endsWith(it, true) }
+
+fun String.isImageFast() = photoExtensions.any { endsWith(it, true) }
+fun String.isAudioFast() = audioExtensions.any { endsWith(it, true) }
+fun String.isRawFast() = rawExtensions.any { endsWith(it, true) }
+
+fun String.isImageSlow() = isImageFast() || getMimeType().startsWith("image")
+fun String.isVideoSlow() = isVideoFast() || getMimeType().startsWith("video")
+fun String.isAudioSlow() = isAudioFast() || getMimeType().startsWith("audio")
+
+fun String.getCompressionFormat() = when (getFilenameExtension().toLowerCase()) {
+ "png" -> Bitmap.CompressFormat.PNG
+ "webp" -> Bitmap.CompressFormat.WEBP
+ else -> Bitmap.CompressFormat.JPEG
+}
+
+fun String.areDigitsOnly() = matches(Regex("[0-9]+"))
+
+fun String.getExifProperties(exif: ExifInterface): String {
+ var exifString = ""
+ exif.getAttribute(ExifInterface.TAG_F_NUMBER).let {
+ if (it?.isNotEmpty() == true) {
+ val number = it.trimEnd('0').trimEnd('.')
+ exifString += "F/$number "
+ }
+ }
+
+ exif.getAttribute(ExifInterface.TAG_FOCAL_LENGTH).let {
+ if (it?.isNotEmpty() == true) {
+ val values = it.split('/')
+ val focalLength = "${values[0].toDouble() / values[1].toDouble()}mm"
+ exifString += "$focalLength "
+ }
+ }
+
+ exif.getAttribute(ExifInterface.TAG_EXPOSURE_TIME).let {
+ if (it?.isNotEmpty() == true) {
+ val exposureSec = Math.round(1 / it.toFloat())
+ exifString += "1/${exposureSec}s "
+ }
+ }
+
+ exif.getAttribute(ExifInterface.TAG_ISO_SPEED_RATINGS).let {
+ if (it?.isNotEmpty() == true) {
+ exifString += "ISO-$it"
+ }
+ }
+
+ return exifString.trim()
+}
+
+fun String.getExifDateTaken(exif: ExifInterface): String {
+ val dateTime = exif.getAttribute(ExifInterface.TAG_DATETIME_ORIGINAL) ?: exif.getAttribute(ExifInterface.TAG_DATETIME)
+ dateTime.let {
+ if (it?.isNotEmpty() == true) {
+ try {
+ val simpleDateFormat = SimpleDateFormat("yyyy:MM:dd kk:mm:ss", Locale.ENGLISH)
+ return simpleDateFormat.parse(it).time.formatDate().trim()
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+ return ""
+}
+
+fun String.getExifCameraModel(exif: ExifInterface): String {
+ exif.getAttribute(ExifInterface.TAG_MAKE).let {
+ if (it?.isNotEmpty() == true) {
+ val model = exif.getAttribute(ExifInterface.TAG_MODEL)
+ return "$it $model".trim()
+ }
+ }
+ return ""
+}
+
+fun String.getGenericMimeType(): String {
+ if (!contains("/"))
+ return this
+
+ val type = substring(0, indexOf("/"))
+ return "$type/*"
+}
+
+fun String.getParentPath(): String {
+ var parent = removeSuffix("/${getFilenameFromPath()}")
+ if (parent == "otg:") {
+ parent = OTG_PATH
+ }
+ return parent
+}
+
+fun String.getDuration() = getFileDurationSeconds()?.getFormattedDuration()
+
+fun String.getFileDurationSeconds(): Int? {
+ return try {
+ val retriever = MediaMetadataRetriever()
+ retriever.setDataSource(this)
+ val time = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
+ val timeInMs = java.lang.Long.parseLong(time)
+ (timeInMs / 1000).toInt()
+ } catch (e: Exception) {
+ null
+ }
+}
+
+fun String.getFileArtist(): String? {
+ return try {
+ val retriever = MediaMetadataRetriever()
+ retriever.setDataSource(this)
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST)
+ } catch (ignored: Exception) {
+ null
+ }
+}
+
+fun String.getFileAlbum(): String? {
+ return try {
+ val retriever = MediaMetadataRetriever()
+ retriever.setDataSource(this)
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM)
+ } catch (ignored: Exception) {
+ null
+ }
+}
+
+fun String.getFileSongTitle(): String? {
+ return try {
+ val retriever = MediaMetadataRetriever()
+ retriever.setDataSource(this)
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE)
+ } catch (ignored: Exception) {
+ null
+ }
+}
+
+fun String.getResolution(): Point? {
+ return if (isImageFast() || isImageSlow()) {
+ getImageResolution()
+ } else if (isVideoFast() || isVideoSlow()) {
+ getVideoResolution()
+ } else {
+ null
+ }
+}
+
+fun String.getVideoResolution(): Point? {
+ return try {
+ val retriever = MediaMetadataRetriever()
+ retriever.setDataSource(this)
+ val width = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH).toInt()
+ val height = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT).toInt()
+ Point(width, height)
+ } catch (ignored: Exception) {
+ null
+ }
+}
+
+fun String.getImageResolution(): Point? {
+ val options = BitmapFactory.Options()
+ options.inJustDecodeBounds = true
+ BitmapFactory.decodeFile(this, options)
+ val width = options.outWidth
+ val height = options.outHeight
+ return if (width > 0 && height > 0) {
+ Point(options.outWidth, options.outHeight)
+ } else {
+ null
+ }
+}
+
+fun String.getPublicUri(context: Context) = context.getDocumentFile(this)?.uri ?: ""
+
+fun String.getOTGPublicPath(context: Context) = "${context.baseConfig.OTGTreeUri}/document/${context.baseConfig.OTGPartition}%3A${substring(OTG_PATH.length).replace("/", "%2F")}"
+
+fun String.substringTo(cnt: Int): String {
+ return if (isEmpty()) {
+ ""
+ } else {
+ substring(0, Math.min(length, cnt))
+ }
+}
+
+fun String.highlightTextPart(textToHighlight: String, color: Int, highlightAll: Boolean = false): SpannableString {
+ val spannableString = SpannableString(this)
+ if (textToHighlight.isEmpty()) {
+ return spannableString
+ }
+
+ var startIndex = normalizeString().indexOf(textToHighlight, 0, true)
+ val indexes = ArrayList()
+ while (startIndex >= 0) {
+ if (startIndex != -1) {
+ indexes.add(startIndex)
+ }
+
+ startIndex = normalizeString().indexOf(textToHighlight, startIndex + textToHighlight.length, true)
+ if (!highlightAll) {
+ break
+ }
+ }
+
+ indexes.forEach {
+ val endIndex = Math.min(it + textToHighlight.length, length)
+ try {
+ spannableString.setSpan(ForegroundColorSpan(color), it, endIndex, Spannable.SPAN_EXCLUSIVE_INCLUSIVE)
+ } catch (ignored: IndexOutOfBoundsException) {
+ }
+ }
+
+ return spannableString
+}
+
+fun String.normalizeString() = Normalizer.normalize(this, Normalizer.Form.NFD).replace(normalizeRegex, "")
+
+fun String.getMimeType(): String {
+ val typesMap = HashMap().apply {
+ put("323", "text/h323")
+ put("3g2", "video/3gpp2")
+ put("3gp", "video/3gpp")
+ put("3gp2", "video/3gpp2")
+ put("3gpp", "video/3gpp")
+ put("7z", "application/x-7z-compressed")
+ put("aa", "audio/audible")
+ put("aac", "audio/aac")
+ put("aaf", "application/octet-stream")
+ put("aax", "audio/vnd.audible.aax")
+ put("ac3", "audio/ac3")
+ put("aca", "application/octet-stream")
+ put("accda", "application/msaccess.addin")
+ put("accdb", "application/msaccess")
+ put("accdc", "application/msaccess.cab")
+ put("accde", "application/msaccess")
+ put("accdr", "application/msaccess.runtime")
+ put("accdt", "application/msaccess")
+ put("accdw", "application/msaccess.webapplication")
+ put("accft", "application/msaccess.ftemplate")
+ put("acx", "application/internet-property-stream")
+ put("addin", "text/xml")
+ put("ade", "application/msaccess")
+ put("adobebridge", "application/x-bridge-url")
+ put("adp", "application/msaccess")
+ put("adt", "audio/vnd.dlna.adts")
+ put("adts", "audio/aac")
+ put("afm", "application/octet-stream")
+ put("ai", "application/postscript")
+ put("aif", "audio/aiff")
+ put("aifc", "audio/aiff")
+ put("aiff", "audio/aiff")
+ put("air", "application/vnd.adobe.air-application-installer-package+zip")
+ put("amc", "application/mpeg")
+ put("anx", "application/annodex")
+ put("apk", "application/vnd.android.package-archive")
+ put("application", "application/x-ms-application")
+ put("art", "image/x-jg")
+ put("asa", "application/xml")
+ put("asax", "application/xml")
+ put("ascx", "application/xml")
+ put("asd", "application/octet-stream")
+ put("asf", "video/x-ms-asf")
+ put("ashx", "application/xml")
+ put("asi", "application/octet-stream")
+ put("asm", "text/plain")
+ put("asmx", "application/xml")
+ put("aspx", "application/xml")
+ put("asr", "video/x-ms-asf")
+ put("asx", "video/x-ms-asf")
+ put("atom", "application/atom+xml")
+ put("au", "audio/basic")
+ put("avi", "video/x-msvideo")
+ put("axa", "audio/annodex")
+ put("axs", "application/olescript")
+ put("axv", "video/annodex")
+ put("bas", "text/plain")
+ put("bcpio", "application/x-bcpio")
+ put("bin", "application/octet-stream")
+ put("bmp", "image/bmp")
+ put("c", "text/plain")
+ put("cab", "application/octet-stream")
+ put("caf", "audio/x-caf")
+ put("calx", "application/vnd.ms-office.calx")
+ put("cat", "application/vnd.ms-pki.seccat")
+ put("cc", "text/plain")
+ put("cd", "text/plain")
+ put("cdda", "audio/aiff")
+ put("cdf", "application/x-cdf")
+ put("cer", "application/x-x509-ca-cert")
+ put("cfg", "text/plain")
+ put("chm", "application/octet-stream")
+ put("class", "application/x-java-applet")
+ put("clp", "application/x-msclip")
+ put("cmd", "text/plain")
+ put("cmx", "image/x-cmx")
+ put("cnf", "text/plain")
+ put("cod", "image/cis-cod")
+ put("config", "application/xml")
+ put("contact", "text/x-ms-contact")
+ put("coverage", "application/xml")
+ put("cpio", "application/x-cpio")
+ put("cpp", "text/plain")
+ put("crd", "application/x-mscardfile")
+ put("crl", "application/pkix-crl")
+ put("crt", "application/x-x509-ca-cert")
+ put("cs", "text/plain")
+ put("csdproj", "text/plain")
+ put("csh", "application/x-csh")
+ put("csproj", "text/plain")
+ put("css", "text/css")
+ put("csv", "text/csv")
+ put("cur", "application/octet-stream")
+ put("cxx", "text/plain")
+ put("dat", "application/octet-stream")
+ put("datasource", "application/xml")
+ put("dbproj", "text/plain")
+ put("dcr", "application/x-director")
+ put("def", "text/plain")
+ put("deploy", "application/octet-stream")
+ put("der", "application/x-x509-ca-cert")
+ put("dgml", "application/xml")
+ put("dib", "image/bmp")
+ put("dif", "video/x-dv")
+ put("dir", "application/x-director")
+ put("disco", "text/xml")
+ put("divx", "video/divx")
+ put("dll", "application/x-msdownload")
+ put("dll.config", "text/xml")
+ put("dlm", "text/dlm")
+ put("dng", "image/x-adobe-dng")
+ put("doc", "application/msword")
+ put("docm", "application/vnd.ms-word.document.macroEnabled.12")
+ put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
+ put("dot", "application/msword")
+ put("dotm", "application/vnd.ms-word.template.macroEnabled.12")
+ put("dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template")
+ put("dsp", "application/octet-stream")
+ put("dsw", "text/plain")
+ put("dtd", "text/xml")
+ put("dtsconfig", "text/xml")
+ put("dv", "video/x-dv")
+ put("dvi", "application/x-dvi")
+ put("dwf", "drawing/x-dwf")
+ put("dwp", "application/octet-stream")
+ put("dxr", "application/x-director")
+ put("eml", "message/rfc822")
+ put("emz", "application/octet-stream")
+ put("eot", "application/vnd.ms-fontobject")
+ put("eps", "application/postscript")
+ put("etl", "application/etl")
+ put("etx", "text/x-setext")
+ put("evy", "application/envoy")
+ put("exe", "application/octet-stream")
+ put("exe.config", "text/xml")
+ put("fdf", "application/vnd.fdf")
+ put("fif", "application/fractals")
+ put("filters", "application/xml")
+ put("fla", "application/octet-stream")
+ put("flac", "audio/flac")
+ put("flr", "x-world/x-vrml")
+ put("flv", "video/x-flv")
+ put("fsscript", "application/fsharp-script")
+ put("fsx", "application/fsharp-script")
+ put("generictest", "application/xml")
+ put("gif", "image/gif")
+ put("group", "text/x-ms-group")
+ put("gsm", "audio/x-gsm")
+ put("gtar", "application/x-gtar")
+ put("gz", "application/x-gzip")
+ put("h", "text/plain")
+ put("hdf", "application/x-hdf")
+ put("hdml", "text/x-hdml")
+ put("hhc", "application/x-oleobject")
+ put("hhk", "application/octet-stream")
+ put("hhp", "application/octet-stream")
+ put("hlp", "application/winhlp")
+ put("hpp", "text/plain")
+ put("hqx", "application/mac-binhex40")
+ put("hta", "application/hta")
+ put("htc", "text/x-component")
+ put("htm", "text/html")
+ put("html", "text/html")
+ put("htt", "text/webviewhtml")
+ put("hxa", "application/xml")
+ put("hxc", "application/xml")
+ put("hxd", "application/octet-stream")
+ put("hxe", "application/xml")
+ put("hxf", "application/xml")
+ put("hxh", "application/octet-stream")
+ put("hxi", "application/octet-stream")
+ put("hxk", "application/xml")
+ put("hxq", "application/octet-stream")
+ put("hxr", "application/octet-stream")
+ put("hxs", "application/octet-stream")
+ put("hxt", "text/html")
+ put("hxv", "application/xml")
+ put("hxw", "application/octet-stream")
+ put("hxx", "text/plain")
+ put("i", "text/plain")
+ put("ico", "image/x-icon")
+ put("ics", "text/calendar")
+ put("idl", "text/plain")
+ put("ief", "image/ief")
+ put("iii", "application/x-iphone")
+ put("inc", "text/plain")
+ put("inf", "application/octet-stream")
+ put("ini", "text/plain")
+ put("inl", "text/plain")
+ put("ins", "application/x-internet-signup")
+ put("ipa", "application/x-itunes-ipa")
+ put("ipg", "application/x-itunes-ipg")
+ put("ipproj", "text/plain")
+ put("ipsw", "application/x-itunes-ipsw")
+ put("iqy", "text/x-ms-iqy")
+ put("isp", "application/x-internet-signup")
+ put("ite", "application/x-itunes-ite")
+ put("itlp", "application/x-itunes-itlp")
+ put("itms", "application/x-itunes-itms")
+ put("itpc", "application/x-itunes-itpc")
+ put("ivf", "video/x-ivf")
+ put("jar", "application/java-archive")
+ put("java", "application/octet-stream")
+ put("jck", "application/liquidmotion")
+ put("jcz", "application/liquidmotion")
+ put("jfif", "image/pjpeg")
+ put("jnlp", "application/x-java-jnlp-file")
+ put("jpb", "application/octet-stream")
+ put("jpe", "image/jpeg")
+ put("jpeg", "image/jpeg")
+ put("jpg", "image/jpeg")
+ put("js", "application/javascript")
+ put("json", "application/json")
+ put("jsx", "text/jscript")
+ put("jsxbin", "text/plain")
+ put("latex", "application/x-latex")
+ put("library-ms", "application/windows-library+xml")
+ put("lit", "application/x-ms-reader")
+ put("loadtest", "application/xml")
+ put("lpk", "application/octet-stream")
+ put("lsf", "video/x-la-asf")
+ put("lst", "text/plain")
+ put("lsx", "video/x-la-asf")
+ put("lzh", "application/octet-stream")
+ put("m13", "application/x-msmediaview")
+ put("m14", "application/x-msmediaview")
+ put("m1v", "video/mpeg")
+ put("m2t", "video/vnd.dlna.mpeg-tts")
+ put("m2ts", "video/vnd.dlna.mpeg-tts")
+ put("m2v", "video/mpeg")
+ put("m3u", "audio/x-mpegurl")
+ put("m3u8", "audio/x-mpegurl")
+ put("m4a", "audio/m4a")
+ put("m4b", "audio/m4b")
+ put("m4p", "audio/m4p")
+ put("m4r", "audio/x-m4r")
+ put("m4v", "video/x-m4v")
+ put("mac", "image/x-macpaint")
+ put("mak", "text/plain")
+ put("man", "application/x-troff-man")
+ put("manifest", "application/x-ms-manifest")
+ put("map", "text/plain")
+ put("master", "application/xml")
+ put("mda", "application/msaccess")
+ put("mdb", "application/x-msaccess")
+ put("mde", "application/msaccess")
+ put("mdp", "application/octet-stream")
+ put("me", "application/x-troff-me")
+ put("mfp", "application/x-shockwave-flash")
+ put("mht", "message/rfc822")
+ put("mhtml", "message/rfc822")
+ put("mid", "audio/mid")
+ put("midi", "audio/mid")
+ put("mix", "application/octet-stream")
+ put("mk", "text/plain")
+ put("mkv", "video/x-matroska")
+ put("mmf", "application/x-smaf")
+ put("mno", "text/xml")
+ put("mny", "application/x-msmoney")
+ put("mod", "video/mpeg")
+ put("mov", "video/quicktime")
+ put("movie", "video/x-sgi-movie")
+ put("mp2", "video/mpeg")
+ put("mp2v", "video/mpeg")
+ put("mp3", "audio/mpeg")
+ put("mp4", "video/mp4")
+ put("mp4v", "video/mp4")
+ put("mpa", "video/mpeg")
+ put("mpe", "video/mpeg")
+ put("mpeg", "video/mpeg")
+ put("mpf", "application/vnd.ms-mediapackage")
+ put("mpg", "video/mpeg")
+ put("mpp", "application/vnd.ms-project")
+ put("mpv2", "video/mpeg")
+ put("mqv", "video/quicktime")
+ put("ms", "application/x-troff-ms")
+ put("msi", "application/octet-stream")
+ put("mso", "application/octet-stream")
+ put("mts", "video/vnd.dlna.mpeg-tts")
+ put("mtx", "application/xml")
+ put("mvb", "application/x-msmediaview")
+ put("mvc", "application/x-miva-compiled")
+ put("mxp", "application/x-mmxp")
+ put("nc", "application/x-netcdf")
+ put("nsc", "video/x-ms-asf")
+ put("nws", "message/rfc822")
+ put("ocx", "application/octet-stream")
+ put("oda", "application/oda")
+ put("odb", "application/vnd.oasis.opendocument.database")
+ put("odc", "application/vnd.oasis.opendocument.chart")
+ put("odf", "application/vnd.oasis.opendocument.formula")
+ put("odg", "application/vnd.oasis.opendocument.graphics")
+ put("odh", "text/plain")
+ put("odi", "application/vnd.oasis.opendocument.image")
+ put("odl", "text/plain")
+ put("odm", "application/vnd.oasis.opendocument.text-master")
+ put("odp", "application/vnd.oasis.opendocument.presentation")
+ put("ods", "application/vnd.oasis.opendocument.spreadsheet")
+ put("odt", "application/vnd.oasis.opendocument.text")
+ put("oga", "audio/ogg")
+ put("ogg", "audio/ogg")
+ put("ogv", "video/ogg")
+ put("ogx", "application/ogg")
+ put("one", "application/onenote")
+ put("onea", "application/onenote")
+ put("onepkg", "application/onenote")
+ put("onetmp", "application/onenote")
+ put("onetoc", "application/onenote")
+ put("onetoc2", "application/onenote")
+ put("opus", "audio/ogg")
+ put("orderedtest", "application/xml")
+ put("osdx", "application/opensearchdescription+xml")
+ put("otf", "application/font-sfnt")
+ put("otg", "application/vnd.oasis.opendocument.graphics-template")
+ put("oth", "application/vnd.oasis.opendocument.text-web")
+ put("otp", "application/vnd.oasis.opendocument.presentation-template")
+ put("ots", "application/vnd.oasis.opendocument.spreadsheet-template")
+ put("ott", "application/vnd.oasis.opendocument.text-template")
+ put("oxt", "application/vnd.openofficeorg.extension")
+ put("p10", "application/pkcs10")
+ put("p12", "application/x-pkcs12")
+ put("p7b", "application/x-pkcs7-certificates")
+ put("p7c", "application/pkcs7-mime")
+ put("p7m", "application/pkcs7-mime")
+ put("p7r", "application/x-pkcs7-certreqresp")
+ put("p7s", "application/pkcs7-signature")
+ put("pbm", "image/x-portable-bitmap")
+ put("pcast", "application/x-podcast")
+ put("pct", "image/pict")
+ put("pcx", "application/octet-stream")
+ put("pcz", "application/octet-stream")
+ put("pdf", "application/pdf")
+ put("pfb", "application/octet-stream")
+ put("pfm", "application/octet-stream")
+ put("pfx", "application/x-pkcs12")
+ put("pgm", "image/x-portable-graymap")
+ put("php", "text/plain")
+ put("pic", "image/pict")
+ put("pict", "image/pict")
+ put("pkgdef", "text/plain")
+ put("pkgundef", "text/plain")
+ put("pko", "application/vnd.ms-pki.pko")
+ put("pls", "audio/scpls")
+ put("pma", "application/x-perfmon")
+ put("pmc", "application/x-perfmon")
+ put("pml", "application/x-perfmon")
+ put("pmr", "application/x-perfmon")
+ put("pmw", "application/x-perfmon")
+ put("png", "image/png")
+ put("pnm", "image/x-portable-anymap")
+ put("pnt", "image/x-macpaint")
+ put("pntg", "image/x-macpaint")
+ put("pnz", "image/png")
+ put("pot", "application/vnd.ms-powerpoint")
+ put("potm", "application/vnd.ms-powerpoint.template.macroEnabled.12")
+ put("potx", "application/vnd.openxmlformats-officedocument.presentationml.template")
+ put("ppa", "application/vnd.ms-powerpoint")
+ put("ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12")
+ put("ppm", "image/x-portable-pixmap")
+ put("pps", "application/vnd.ms-powerpoint")
+ put("ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12")
+ put("ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow")
+ put("ppt", "application/vnd.ms-powerpoint")
+ put("pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12")
+ put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation")
+ put("prf", "application/pics-rules")
+ put("prm", "application/octet-stream")
+ put("prx", "application/octet-stream")
+ put("ps", "application/postscript")
+ put("psc1", "application/PowerShell")
+ put("psd", "application/octet-stream")
+ put("psess", "application/xml")
+ put("psm", "application/octet-stream")
+ put("psp", "application/octet-stream")
+ put("pub", "application/x-mspublisher")
+ put("pwz", "application/vnd.ms-powerpoint")
+ put("py", "text/plain")
+ put("qht", "text/x-html-insertion")
+ put("qhtm", "text/x-html-insertion")
+ put("qt", "video/quicktime")
+ put("qti", "image/x-quicktime")
+ put("qtif", "image/x-quicktime")
+ put("qtl", "application/x-quicktimeplayer")
+ put("qxd", "application/octet-stream")
+ put("ra", "audio/x-pn-realaudio")
+ put("ram", "audio/x-pn-realaudio")
+ put("rar", "application/x-rar-compressed")
+ put("ras", "image/x-cmu-raster")
+ put("rat", "application/rat-file")
+ put("rb", "text/plain")
+ put("rc", "text/plain")
+ put("rc2", "text/plain")
+ put("rct", "text/plain")
+ put("rdlc", "application/xml")
+ put("reg", "text/plain")
+ put("resx", "application/xml")
+ put("rf", "image/vnd.rn-realflash")
+ put("rgb", "image/x-rgb")
+ put("rgs", "text/plain")
+ put("rm", "application/vnd.rn-realmedia")
+ put("rmi", "audio/mid")
+ put("rmp", "application/vnd.rn-rn_music_package")
+ put("roff", "application/x-troff")
+ put("rpm", "audio/x-pn-realaudio-plugin")
+ put("rqy", "text/x-ms-rqy")
+ put("rtf", "application/rtf")
+ put("rtx", "text/richtext")
+ put("ruleset", "application/xml")
+ put("s", "text/plain")
+ put("safariextz", "application/x-safari-safariextz")
+ put("scd", "application/x-msschedule")
+ put("scr", "text/plain")
+ put("sct", "text/scriptlet")
+ put("sd2", "audio/x-sd2")
+ put("sdp", "application/sdp")
+ put("sea", "application/octet-stream")
+ put("searchConnector-ms", "application/windows-search-connector+xml")
+ put("setpay", "application/set-payment-initiation")
+ put("setreg", "application/set-registration-initiation")
+ put("settings", "application/xml")
+ put("sgimb", "application/x-sgimb")
+ put("sgml", "text/sgml")
+ put("sh", "application/x-sh")
+ put("shar", "application/x-shar")
+ put("shtml", "text/html")
+ put("sit", "application/x-stuffit")
+ put("sitemap", "application/xml")
+ put("skin", "application/xml")
+ put("sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12")
+ put("sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide")
+ put("slk", "application/vnd.ms-excel")
+ put("sln", "text/plain")
+ put("slupkg-ms", "application/x-ms-license")
+ put("smd", "audio/x-smd")
+ put("smi", "application/octet-stream")
+ put("smx", "audio/x-smd")
+ put("smz", "audio/x-smd")
+ put("snd", "audio/basic")
+ put("snippet", "application/xml")
+ put("snp", "application/octet-stream")
+ put("sol", "text/plain")
+ put("sor", "text/plain")
+ put("spc", "application/x-pkcs7-certificates")
+ put("spl", "application/futuresplash")
+ put("spx", "audio/ogg")
+ put("src", "application/x-wais-source")
+ put("srf", "text/plain")
+ put("ssisdeploymentmanifest", "text/xml")
+ put("ssm", "application/streamingmedia")
+ put("sst", "application/vnd.ms-pki.certstore")
+ put("stl", "application/vnd.ms-pki.stl")
+ put("sv4cpio", "application/x-sv4cpio")
+ put("sv4crc", "application/x-sv4crc")
+ put("svc", "application/xml")
+ put("svg", "image/svg+xml")
+ put("swf", "application/x-shockwave-flash")
+ put("t", "application/x-troff")
+ put("tar", "application/x-tar")
+ put("tcl", "application/x-tcl")
+ put("testrunconfig", "application/xml")
+ put("testsettings", "application/xml")
+ put("tex", "application/x-tex")
+ put("texi", "application/x-texinfo")
+ put("texinfo", "application/x-texinfo")
+ put("tgz", "application/x-compressed")
+ put("thmx", "application/vnd.ms-officetheme")
+ put("thn", "application/octet-stream")
+ put("tif", "image/tiff")
+ put("tiff", "image/tiff")
+ put("tlh", "text/plain")
+ put("tli", "text/plain")
+ put("toc", "application/octet-stream")
+ put("tr", "application/x-troff")
+ put("trm", "application/x-msterminal")
+ put("trx", "application/xml")
+ put("ts", "video/vnd.dlna.mpeg-tts")
+ put("tsv", "text/tab-separated-values")
+ put("ttf", "application/font-sfnt")
+ put("tts", "video/vnd.dlna.mpeg-tts")
+ put("txt", "text/plain")
+ put("u32", "application/octet-stream")
+ put("uls", "text/iuls")
+ put("user", "text/plain")
+ put("ustar", "application/x-ustar")
+ put("vb", "text/plain")
+ put("vbdproj", "text/plain")
+ put("vbk", "video/mpeg")
+ put("vbproj", "text/plain")
+ put("vbs", "text/vbscript")
+ put("vcf", "text/x-vcard")
+ put("vcproj", "application/xml")
+ put("vcs", "text/plain")
+ put("vcxproj", "application/xml")
+ put("vddproj", "text/plain")
+ put("vdp", "text/plain")
+ put("vdproj", "text/plain")
+ put("vdx", "application/vnd.ms-visio.viewer")
+ put("vml", "text/xml")
+ put("vscontent", "application/xml")
+ put("vsct", "text/xml")
+ put("vsd", "application/vnd.visio")
+ put("vsi", "application/ms-vsi")
+ put("vsix", "application/vsix")
+ put("vsixlangpack", "text/xml")
+ put("vsixmanifest", "text/xml")
+ put("vsmdi", "application/xml")
+ put("vspscc", "text/plain")
+ put("vss", "application/vnd.visio")
+ put("vsscc", "text/plain")
+ put("vssettings", "text/xml")
+ put("vssscc", "text/plain")
+ put("vst", "application/vnd.visio")
+ put("vstemplate", "text/xml")
+ put("vsto", "application/x-ms-vsto")
+ put("vsw", "application/vnd.visio")
+ put("vsx", "application/vnd.visio")
+ put("vtx", "application/vnd.visio")
+ put("wav", "audio/wav")
+ put("wave", "audio/wav")
+ put("wax", "audio/x-ms-wax")
+ put("wbk", "application/msword")
+ put("wbmp", "image/vnd.wap.wbmp")
+ put("wcm", "application/vnd.ms-works")
+ put("wdb", "application/vnd.ms-works")
+ put("wdp", "image/vnd.ms-photo")
+ put("webarchive", "application/x-safari-webarchive")
+ put("webm", "video/webm")
+ put("webp", "image/webp")
+ put("webtest", "application/xml")
+ put("wiq", "application/xml")
+ put("wiz", "application/msword")
+ put("wks", "application/vnd.ms-works")
+ put("wlmp", "application/wlmoviemaker")
+ put("wlpginstall", "application/x-wlpg-detect")
+ put("wlpginstall3", "application/x-wlpg3-detect")
+ put("wm", "video/x-ms-wm")
+ put("wma", "audio/x-ms-wma")
+ put("wmd", "application/x-ms-wmd")
+ put("wmf", "application/x-msmetafile")
+ put("wml", "text/vnd.wap.wml")
+ put("wmlc", "application/vnd.wap.wmlc")
+ put("wmls", "text/vnd.wap.wmlscript")
+ put("wmlsc", "application/vnd.wap.wmlscriptc")
+ put("wmp", "video/x-ms-wmp")
+ put("wmv", "video/x-ms-wmv")
+ put("wmx", "video/x-ms-wmx")
+ put("wmz", "application/x-ms-wmz")
+ put("woff", "application/font-woff")
+ put("wpl", "application/vnd.ms-wpl")
+ put("wps", "application/vnd.ms-works")
+ put("wri", "application/x-mswrite")
+ put("wrl", "x-world/x-vrml")
+ put("wrz", "x-world/x-vrml")
+ put("wsc", "text/scriptlet")
+ put("wsdl", "text/xml")
+ put("wvx", "video/x-ms-wvx")
+ put("x", "application/directx")
+ put("xaf", "x-world/x-vrml")
+ put("xaml", "application/xaml+xml")
+ put("xap", "application/x-silverlight-app")
+ put("xbap", "application/x-ms-xbap")
+ put("xbm", "image/x-xbitmap")
+ put("xdr", "text/plain")
+ put("xht", "application/xhtml+xml")
+ put("xhtml", "application/xhtml+xml")
+ put("xla", "application/vnd.ms-excel")
+ put("xlam", "application/vnd.ms-excel.addin.macroEnabled.12")
+ put("xlc", "application/vnd.ms-excel")
+ put("xld", "application/vnd.ms-excel")
+ put("xlk", "application/vnd.ms-excel")
+ put("xll", "application/vnd.ms-excel")
+ put("xlm", "application/vnd.ms-excel")
+ put("xls", "application/vnd.ms-excel")
+ put("xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12")
+ put("xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12")
+ put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
+ put("xlt", "application/vnd.ms-excel")
+ put("xltm", "application/vnd.ms-excel.template.macroEnabled.12")
+ put("xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template")
+ put("xlw", "application/vnd.ms-excel")
+ put("xml", "text/xml")
+ put("xmta", "application/xml")
+ put("xof", "x-world/x-vrml")
+ put("xoml", "text/plain")
+ put("xpm", "image/x-xpixmap")
+ put("xps", "application/vnd.ms-xpsdocument")
+ put("xrm-ms", "text/xml")
+ put("xsc", "application/xml")
+ put("xsd", "text/xml")
+ put("xsf", "text/xml")
+ put("xsl", "text/xml")
+ put("xslt", "text/xml")
+ put("xsn", "application/octet-stream")
+ put("xss", "application/xml")
+ put("xspf", "application/xspf+xml")
+ put("xtp", "application/octet-stream")
+ put("xwd", "image/x-xwindowdump")
+ put("z", "application/x-compress")
+ put("zip", "application/zip")
+ }
+ return typesMap[getFilenameExtension().toLowerCase()] ?: ""
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/TextView.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/TextView.kt
new file mode 100644
index 0000000..ce9d6a3
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/TextView.kt
@@ -0,0 +1,10 @@
+package com.simplemobiletools.commons.extensions
+
+import android.graphics.Paint
+import android.widget.TextView
+
+val TextView.value: String get() = text.toString().trim()
+
+fun TextView.underlineText() {
+ paintFlags = paintFlags or Paint.UNDERLINE_TEXT_FLAG
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/View.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/View.kt
new file mode 100644
index 0000000..a6d1166
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/View.kt
@@ -0,0 +1,40 @@
+package com.simplemobiletools.commons.extensions
+
+import android.view.HapticFeedbackConstants
+import android.view.View
+import android.view.ViewTreeObserver
+
+fun View.beInvisibleIf(beInvisible: Boolean) = if (beInvisible) beInvisible() else beVisible()
+
+fun View.beVisibleIf(beVisible: Boolean) = if (beVisible) beVisible() else beGone()
+
+fun View.beGoneIf(beGone: Boolean) = beVisibleIf(!beGone)
+
+fun View.beInvisible() {
+ visibility = View.INVISIBLE
+}
+
+fun View.beVisible() {
+ visibility = View.VISIBLE
+}
+
+fun View.beGone() {
+ visibility = View.GONE
+}
+
+fun View.onGlobalLayout(callback: () -> Unit) {
+ viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
+ override fun onGlobalLayout() {
+ viewTreeObserver.removeOnGlobalLayoutListener(this)
+ callback()
+ }
+ })
+}
+
+fun View.isVisible() = visibility == View.VISIBLE
+
+fun View.isInvisible() = visibility == View.INVISIBLE
+
+fun View.isGone() = visibility == View.GONE
+
+fun View.performHapticFeedback() = performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ViewPager.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ViewPager.kt
new file mode 100644
index 0000000..3239a1d
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/extensions/ViewPager.kt
@@ -0,0 +1,16 @@
+package com.simplemobiletools.commons.extensions
+
+import android.support.v4.view.ViewPager
+
+fun ViewPager.onPageChangeListener(pageChangedAction: (newPosition: Int) -> Unit) =
+ addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
+ override fun onPageScrollStateChanged(state: Int) {
+ }
+
+ override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
+ }
+
+ override fun onPageSelected(position: Int) {
+ pageChangedAction(position)
+ }
+ })
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/AlphanumericComparator.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/AlphanumericComparator.kt
new file mode 100644
index 0000000..d32f459
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/AlphanumericComparator.kt
@@ -0,0 +1,77 @@
+package com.simplemobiletools.commons.helpers
+
+// taken from https://gist.github.com/MichaelRocks/1b94bb44c7804e999dbf31dac86955ec
+// make IMG_5.jpg come before IMG_10.jpg
+class AlphanumericComparator {
+ fun compare(string1: String, string2: String): Int {
+ var thisMarker = 0
+ var thatMarker = 0
+ val s1Length = string1.length
+ val s2Length = string2.length
+
+ while (thisMarker < s1Length && thatMarker < s2Length) {
+ val thisChunk = getChunk(string1, s1Length, thisMarker)
+ thisMarker += thisChunk.length
+
+ val thatChunk = getChunk(string2, s2Length, thatMarker)
+ thatMarker += thatChunk.length
+
+ // If both chunks contain numeric characters, sort them numerically.
+ var result: Int
+ if (isDigit(thisChunk[0]) && isDigit(thatChunk[0])) {
+ // Simple chunk comparison by length.
+ val thisChunkLength = thisChunk.length
+ result = thisChunkLength - thatChunk.length
+ // If equal, the first different number counts.
+ if (result == 0) {
+ for (i in 0..thisChunkLength - 1) {
+ result = thisChunk[i] - thatChunk[i]
+ if (result != 0) {
+ return result
+ }
+ }
+ }
+ } else {
+ result = thisChunk.compareTo(thatChunk)
+ }
+
+ if (result != 0) {
+ return result
+ }
+ }
+
+ return s1Length - s2Length
+ }
+
+ private fun getChunk(string: String, length: Int, marker: Int): String {
+ var current = marker
+ val chunk = StringBuilder()
+ var c = string[current]
+ chunk.append(c)
+ current++
+ if (isDigit(c)) {
+ while (current < length) {
+ c = string[current]
+ if (!isDigit(c)) {
+ break
+ }
+ chunk.append(c)
+ current++
+ }
+ } else {
+ while (current < length) {
+ c = string[current]
+ if (isDigit(c)) {
+ break
+ }
+ chunk.append(c)
+ current++
+ }
+ }
+ return chunk.toString()
+ }
+
+ private fun isDigit(ch: Char): Boolean {
+ return ch in '0'..'9'
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/BaseConfig.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/BaseConfig.kt
new file mode 100644
index 0000000..46642b3
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/BaseConfig.kt
@@ -0,0 +1,269 @@
+package com.simplemobiletools.commons.helpers
+
+import android.content.Context
+import android.text.format.DateFormat
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.getInternalStoragePath
+import com.simplemobiletools.commons.extensions.getSDCardPath
+import com.simplemobiletools.commons.extensions.getSharedPrefs
+import java.util.*
+
+open class BaseConfig(val context: Context) {
+ protected val prefs = context.getSharedPrefs()
+
+ companion object {
+ fun newInstance(context: Context) = BaseConfig(context)
+ }
+
+ var appRunCount: Int
+ get() = prefs.getInt(APP_RUN_COUNT, 0)
+ set(appRunCount) = prefs.edit().putInt(APP_RUN_COUNT, appRunCount).apply()
+
+ var lastVersion: Int
+ get() = prefs.getInt(LAST_VERSION, 0)
+ set(lastVersion) = prefs.edit().putInt(LAST_VERSION, lastVersion).apply()
+
+ var treeUri: String
+ get() = prefs.getString(TREE_URI, "")
+ set(uri) = prefs.edit().putString(TREE_URI, uri).apply()
+
+ var OTGTreeUri: String
+ get() = prefs.getString(OTG_TREE_URI, "")
+ set(OTGTreeUri) = prefs.edit().putString(OTG_TREE_URI, OTGTreeUri).apply()
+
+ var OTGPartition: String
+ get() = prefs.getString(OTG_PARTITION, "")
+ set(OTGPartition) = prefs.edit().putString(OTG_PARTITION, OTGPartition).apply()
+
+ var sdCardPath: String
+ get() = prefs.getString(SD_CARD_PATH, getDefaultSDCardPath())
+ set(sdCardPath) = prefs.edit().putString(SD_CARD_PATH, sdCardPath).apply()
+
+ private fun getDefaultSDCardPath() = if (prefs.contains(SD_CARD_PATH)) "" else context.getSDCardPath()
+
+ var internalStoragePath: String
+ get() = prefs.getString(INTERNAL_STORAGE_PATH, getDefaultInternalPath())
+ set(internalStoragePath) = prefs.edit().putString(INTERNAL_STORAGE_PATH, internalStoragePath).apply()
+
+ private fun getDefaultInternalPath() = if (prefs.contains(INTERNAL_STORAGE_PATH)) "" else context.getInternalStoragePath()
+
+ var textColor: Int
+ get() = prefs.getInt(TEXT_COLOR, context.resources.getColor(R.color.default_text_color))
+ set(textColor) = prefs.edit().putInt(TEXT_COLOR, textColor).apply()
+
+ var backgroundColor: Int
+ get() = prefs.getInt(BACKGROUND_COLOR, context.resources.getColor(R.color.default_background_color))
+ set(backgroundColor) = prefs.edit().putInt(BACKGROUND_COLOR, backgroundColor).apply()
+
+ var primaryColor: Int
+ get() = prefs.getInt(PRIMARY_COLOR, context.resources.getColor(R.color.color_primary))
+ set(primaryColor) = prefs.edit().putInt(PRIMARY_COLOR, primaryColor).apply()
+
+ var appIconColor: Int
+ get() = prefs.getInt(APP_ICON_COLOR, context.resources.getColor(R.color.color_primary))
+ set(appIconColor) {
+ isUsingModifiedAppIcon = appIconColor != context.resources.getColor(R.color.color_primary)
+ prefs.edit().putInt(APP_ICON_COLOR, appIconColor).apply()
+ }
+
+ var lastIconColor: Int
+ get() = prefs.getInt(LAST_ICON_COLOR, context.resources.getColor(R.color.color_primary))
+ set(lastIconColor) = prefs.edit().putInt(LAST_ICON_COLOR, lastIconColor).apply()
+
+ var customTextColor: Int
+ get() = prefs.getInt(CUSTOM_TEXT_COLOR, textColor)
+ set(customTextColor) = prefs.edit().putInt(CUSTOM_TEXT_COLOR, customTextColor).apply()
+
+ var customBackgroundColor: Int
+ get() = prefs.getInt(CUSTOM_BACKGROUND_COLOR, backgroundColor)
+ set(customBackgroundColor) = prefs.edit().putInt(CUSTOM_BACKGROUND_COLOR, customBackgroundColor).apply()
+
+ var customPrimaryColor: Int
+ get() = prefs.getInt(CUSTOM_PRIMARY_COLOR, primaryColor)
+ set(customPrimaryColor) = prefs.edit().putInt(CUSTOM_PRIMARY_COLOR, customPrimaryColor).apply()
+
+ var customAppIconColor: Int
+ get() = prefs.getInt(CUSTOM_APP_ICON_COLOR, primaryColor)
+ set(customPrimaryColor) = prefs.edit().putInt(CUSTOM_PRIMARY_COLOR, customPrimaryColor).apply()
+
+ var widgetBgColor: Int
+ get() = prefs.getInt(WIDGET_BG_COLOR, 1)
+ set(widgetBgColor) = prefs.edit().putInt(WIDGET_BG_COLOR, widgetBgColor).apply()
+
+ var widgetTextColor: Int
+ get() = prefs.getInt(WIDGET_TEXT_COLOR, context.resources.getColor(R.color.color_primary))
+ set(widgetTextColor) = prefs.edit().putInt(WIDGET_TEXT_COLOR, widgetTextColor).apply()
+
+ // hidden folder visibility protection
+ var isPasswordProtectionOn: Boolean
+ get() = prefs.getBoolean(PASSWORD_PROTECTION, false)
+ set(isPasswordProtectionOn) = prefs.edit().putBoolean(PASSWORD_PROTECTION, isPasswordProtectionOn).apply()
+
+ var passwordHash: String
+ get() = prefs.getString(PASSWORD_HASH, "")
+ set(passwordHash) = prefs.edit().putString(PASSWORD_HASH, passwordHash).apply()
+
+ var protectionType: Int
+ get() = prefs.getInt(PROTECTION_TYPE, PROTECTION_PATTERN)
+ set(protectionType) = prefs.edit().putInt(PROTECTION_TYPE, protectionType).apply()
+
+ // whole app launch protection
+ var appPasswordProtectionOn: Boolean
+ get() = prefs.getBoolean(APP_PASSWORD_PROTECTION, false)
+ set(appPasswordProtectionOn) = prefs.edit().putBoolean(APP_PASSWORD_PROTECTION, appPasswordProtectionOn).apply()
+
+ var appPasswordHash: String
+ get() = prefs.getString(APP_PASSWORD_HASH, "")
+ set(appPasswordHash) = prefs.edit().putString(APP_PASSWORD_HASH, appPasswordHash).apply()
+
+ var appProtectionType: Int
+ get() = prefs.getInt(APP_PROTECTION_TYPE, PROTECTION_PATTERN)
+ set(appProtectionType) = prefs.edit().putInt(APP_PROTECTION_TYPE, appProtectionType).apply()
+
+ var keepLastModified: Boolean
+ get() = prefs.getBoolean(KEEP_LAST_MODIFIED, true)
+ set(keepLastModified) = prefs.edit().putBoolean(KEEP_LAST_MODIFIED, keepLastModified).apply()
+
+ var useEnglish: Boolean
+ get() = prefs.getBoolean(USE_ENGLISH, false)
+ set(useEnglish) {
+ wasUseEnglishToggled = true
+ prefs.edit().putBoolean(USE_ENGLISH, useEnglish).commit()
+ }
+
+ var wasUseEnglishToggled: Boolean
+ get() = prefs.getBoolean(WAS_USE_ENGLISH_TOGGLED, false)
+ set(wasUseEnglishToggled) = prefs.edit().putBoolean(WAS_USE_ENGLISH_TOGGLED, wasUseEnglishToggled).apply()
+
+ var wasSharedThemeEverActivated: Boolean
+ get() = prefs.getBoolean(WAS_SHARED_THEME_EVER_ACTIVATED, false)
+ set(wasSharedThemeEverActivated) = prefs.edit().putBoolean(WAS_SHARED_THEME_EVER_ACTIVATED, wasSharedThemeEverActivated).apply()
+
+ var isUsingSharedTheme: Boolean
+ get() = prefs.getBoolean(IS_USING_SHARED_THEME, false)
+ set(isUsingSharedTheme) = prefs.edit().putBoolean(IS_USING_SHARED_THEME, isUsingSharedTheme).apply()
+
+ var wasCustomThemeSwitchDescriptionShown: Boolean
+ get() = prefs.getBoolean(WAS_CUSTOM_THEME_SWITCH_DESCRIPTION_SHOWN, false)
+ set(wasCustomThemeSwitchDescriptionShown) = prefs.edit().putBoolean(WAS_CUSTOM_THEME_SWITCH_DESCRIPTION_SHOWN, wasCustomThemeSwitchDescriptionShown).apply()
+
+ var wasSharedThemeForced: Boolean
+ get() = prefs.getBoolean(WAS_SHARED_THEME_FORCED, false)
+ set(wasSharedThemeForced) = prefs.edit().putBoolean(WAS_SHARED_THEME_FORCED, wasSharedThemeForced).apply()
+
+ // used only for checking shared theme after updating to 3.0.0 from some previous version
+ var wasSharedThemeAfterUpdateChecked: Boolean
+ get() = prefs.getBoolean(WAS_SHARED_THEME_AFTER_UPDATE_CHECKED, false)
+ set(wasSharedThemeAfterUpdateChecked) = prefs.edit().putBoolean(WAS_SHARED_THEME_AFTER_UPDATE_CHECKED, wasSharedThemeAfterUpdateChecked).apply()
+
+ var showInfoBubble: Boolean
+ get() = prefs.getBoolean(SHOW_INFO_BUBBLE, true)
+ set(showInfoBubble) = prefs.edit().putBoolean(SHOW_INFO_BUBBLE, showInfoBubble).apply()
+
+ var lastConflictApplyToAll: Boolean
+ get() = prefs.getBoolean(LAST_CONFLICT_APPLY_TO_ALL, true)
+ set(lastConflictApplyToAll) = prefs.edit().putBoolean(LAST_CONFLICT_APPLY_TO_ALL, lastConflictApplyToAll).apply()
+
+ var lastConflictResolution: Int
+ get() = prefs.getInt(LAST_CONFLICT_RESOLUTION, CONFLICT_SKIP)
+ set(lastConflictResolution) = prefs.edit().putInt(LAST_CONFLICT_RESOLUTION, lastConflictResolution).apply()
+
+ var avoidWhatsNew: Boolean
+ get() = prefs.getBoolean(AVOID_WHATS_NEW, false)
+ set(avoidWhatsNew) = prefs.edit().putBoolean(AVOID_WHATS_NEW, avoidWhatsNew).apply()
+
+ var sorting: Int
+ get() = prefs.getInt(SORT_ORDER, context.resources.getInteger(R.integer.default_sorting))
+ set(sorting) = prefs.edit().putInt(SORT_ORDER, sorting).apply()
+
+ var hadThankYouInstalled: Boolean
+ get() = prefs.getBoolean(HAD_THANK_YOU_INSTALLED, false)
+ set(hadThankYouInstalled) = prefs.edit().putBoolean(HAD_THANK_YOU_INSTALLED, hadThankYouInstalled).apply()
+
+ var skipDeleteConfirmation: Boolean
+ get() = prefs.getBoolean(SKIP_DELETE_CONFIRMATION, false)
+ set(skipDeleteConfirmation) = prefs.edit().putBoolean(SKIP_DELETE_CONFIRMATION, skipDeleteConfirmation).apply()
+
+ var enablePullToRefresh: Boolean
+ get() = prefs.getBoolean(ENABLE_PULL_TO_REFRESH, true)
+ set(enablePullToRefresh) = prefs.edit().putBoolean(ENABLE_PULL_TO_REFRESH, enablePullToRefresh).apply()
+
+ var scrollHorizontally: Boolean
+ get() = prefs.getBoolean(SCROLL_HORIZONTALLY, false)
+ set(scrollHorizontally) = prefs.edit().putBoolean(SCROLL_HORIZONTALLY, scrollHorizontally).apply()
+
+ var preventPhoneFromSleeping: Boolean
+ get() = prefs.getBoolean(PREVENT_PHONE_FROM_SLEEPING, true)
+ set(preventPhoneFromSleeping) = prefs.edit().putBoolean(PREVENT_PHONE_FROM_SLEEPING, preventPhoneFromSleeping).apply()
+
+ var lastUsedViewPagerPage: Int
+ get() = prefs.getInt(LAST_USED_VIEW_PAGER_PAGE, 0)
+ set(lastUsedViewPagerPage) = prefs.edit().putInt(LAST_USED_VIEW_PAGER_PAGE, lastUsedViewPagerPage).apply()
+
+ var use24HourFormat: Boolean
+ get() = prefs.getBoolean(USE_24_HOUR_FORMAT, DateFormat.is24HourFormat(context))
+ set(use24HourFormat) = prefs.edit().putBoolean(USE_24_HOUR_FORMAT, use24HourFormat).apply()
+
+ var isSundayFirst: Boolean
+ get() {
+ val isSundayFirst = Calendar.getInstance(Locale.getDefault()).firstDayOfWeek == Calendar.SUNDAY
+ return prefs.getBoolean(SUNDAY_FIRST, isSundayFirst)
+ }
+ set(sundayFirst) = prefs.edit().putBoolean(SUNDAY_FIRST, sundayFirst).apply()
+
+ var wasAlarmWarningShown: Boolean
+ get() = prefs.getBoolean(WAS_ALARM_WARNING_SHOWN, false)
+ set(wasAlarmWarningShown) = prefs.edit().putBoolean(WAS_ALARM_WARNING_SHOWN, wasAlarmWarningShown).apply()
+
+ var wasReminderWarningShown: Boolean
+ get() = prefs.getBoolean(WAS_REMINDER_WARNING_SHOWN, false)
+ set(wasReminderWarningShown) = prefs.edit().putBoolean(WAS_REMINDER_WARNING_SHOWN, wasReminderWarningShown).apply()
+
+ var useSameSnooze: Boolean
+ get() = prefs.getBoolean(USE_SAME_SNOOZE, true)
+ set(useSameSnooze) = prefs.edit().putBoolean(USE_SAME_SNOOZE, useSameSnooze).apply()
+
+ var snoozeTime: Int
+ get() = prefs.getInt(SNOOZE_TIME, 10)
+ set(snoozeDelay) = prefs.edit().putInt(SNOOZE_TIME, snoozeDelay).apply()
+
+ var vibrateOnButtonPress: Boolean
+ get() = prefs.getBoolean(VIBRATE_ON_BUTTON_PRESS, false)
+ set(vibrateOnButton) = prefs.edit().putBoolean(VIBRATE_ON_BUTTON_PRESS, vibrateOnButton).apply()
+
+ var yourAlarmSounds: String
+ get() = prefs.getString(YOUR_ALARM_SOUNDS, "")
+ set(yourAlarmSounds) = prefs.edit().putString(YOUR_ALARM_SOUNDS, yourAlarmSounds).apply()
+
+ var forcePortrait: Boolean
+ get() = prefs.getBoolean(FORCE_PORTRAIT, true)
+ set(forcePortrait) = prefs.edit().putBoolean(FORCE_PORTRAIT, forcePortrait).apply()
+
+ var isUsingModifiedAppIcon: Boolean
+ get() = prefs.getBoolean(IS_USING_MODIFIED_APP_ICON, false)
+ set(isUsingModifiedAppIcon) = prefs.edit().putBoolean(IS_USING_MODIFIED_APP_ICON, isUsingModifiedAppIcon).apply()
+
+ var appId: String
+ get() = prefs.getString(APP_ID, "")
+ set(appId) = prefs.edit().putString(APP_ID, appId).apply()
+
+ var initialWidgetHeight: Int
+ get() = prefs.getInt(INITIAL_WIDGET_HEIGHT, 0)
+ set(initialWidgetHeight) = prefs.edit().putInt(INITIAL_WIDGET_HEIGHT, initialWidgetHeight).apply()
+
+ var widgetIdToMeasure: Int
+ get() = prefs.getInt(WIDGET_ID_TO_MEASURE, 0)
+ set(widgetIdToMeasure) = prefs.edit().putInt(WIDGET_ID_TO_MEASURE, widgetIdToMeasure).apply()
+
+ var wasOrangeIconChecked: Boolean
+ get() = prefs.getBoolean(WAS_ORANGE_ICON_CHECKED, false)
+ set(wasOrangeIconChecked) = prefs.edit().putBoolean(WAS_ORANGE_ICON_CHECKED, wasOrangeIconChecked).apply()
+
+ var wasAppOnSDShown: Boolean
+ get() = prefs.getBoolean(WAS_APP_ON_SD_SHOWN, false)
+ set(wasAppOnSDShown) = prefs.edit().putBoolean(WAS_APP_ON_SD_SHOWN, wasAppOnSDShown).apply()
+
+ var wasBeforeAskingShown: Boolean
+ get() = prefs.getBoolean(WAS_BEFORE_ASKING_SHOWN, false)
+ set(wasBeforeAskingShown) = prefs.edit().putBoolean(WAS_BEFORE_ASKING_SHOWN, wasBeforeAskingShown).apply()
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/Constants.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/Constants.kt
new file mode 100644
index 0000000..6cf11e1
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/Constants.kt
@@ -0,0 +1,240 @@
+package com.simplemobiletools.commons.helpers
+
+import android.os.Build
+import android.os.Looper
+
+const val APP_NAME = "app_name"
+const val APP_LICENSES = "app_licenses"
+const val APP_FAQ = "app_faq"
+const val APP_VERSION_NAME = "app_version_name"
+const val APP_ICON_IDS = "app_icon_ids"
+const val APP_ID = "app_id"
+const val APP_LAUNCHER_NAME = "app_launcher_name"
+const val REAL_FILE_PATH = "real_file_path_2"
+const val IS_FROM_GALLERY = "is_from_gallery"
+const val BROADCAST_REFRESH_MEDIA = "com.simplemobiletools.REFRESH_MEDIA"
+const val REFRESH_PATH = "refresh_path"
+const val OTG_PATH = "otg:/"
+const val IS_CUSTOMIZING_COLORS = "is_customizing_colors"
+const val ALARM_SOUND_TYPE_ALARM = 1
+const val ALARM_SOUND_TYPE_NOTIFICATION = 2
+const val YOUR_ALARM_SOUNDS_MIN_ID = 1000
+const val SHOW_FAQ_BEFORE_MAIL = "show_faq_before_mail"
+
+const val HOUR_MINUTES = 60
+const val DAY_MINUTES = 24 * HOUR_MINUTES
+const val WEEK_MINUTES = DAY_MINUTES * 7
+const val MONTH_MINUTES = DAY_MINUTES * 30
+const val YEAR_MINUTES = DAY_MINUTES * 365
+
+const val MINUTE_SECONDS = 60
+const val HOUR_SECONDS = HOUR_MINUTES * 60
+const val DAY_SECONDS = DAY_MINUTES * 60
+const val WEEK_SECONDS = WEEK_MINUTES * 60
+const val MONTH_SECONDS = MONTH_MINUTES * 60
+const val YEAR_SECONDS = YEAR_MINUTES * 60
+
+// shared preferences
+const val PREFS_KEY = "Prefs"
+const val APP_RUN_COUNT = "app_run_count"
+const val LAST_VERSION = "last_version"
+const val TREE_URI = "tree_uri_2"
+const val OTG_TREE_URI = "otg_tree_uri"
+const val SD_CARD_PATH = "sd_card_path_2"
+const val INTERNAL_STORAGE_PATH = "internal_storage_path"
+const val TEXT_COLOR = "text_color"
+const val BACKGROUND_COLOR = "background_color"
+const val PRIMARY_COLOR = "primary_color_2"
+const val APP_ICON_COLOR = "app_icon_color"
+const val LAST_ICON_COLOR = "last_icon_color"
+const val CUSTOM_TEXT_COLOR = "custom_text_color"
+const val CUSTOM_BACKGROUND_COLOR = "custom_background_color"
+const val CUSTOM_PRIMARY_COLOR = "custom_primary_color"
+const val CUSTOM_APP_ICON_COLOR = "custom_app_icon_color"
+const val WIDGET_BG_COLOR = "widget_bg_color"
+const val WIDGET_TEXT_COLOR = "widget_text_color"
+const val PASSWORD_PROTECTION = "password_protection"
+const val PASSWORD_HASH = "password_hash"
+const val PROTECTION_TYPE = "protection_type"
+const val APP_PASSWORD_PROTECTION = "app_password_protection"
+const val APP_PASSWORD_HASH = "app_password_hash"
+const val APP_PROTECTION_TYPE = "app_protection_type"
+const val KEEP_LAST_MODIFIED = "keep_last_modified"
+const val USE_ENGLISH = "use_english"
+const val WAS_USE_ENGLISH_TOGGLED = "was_use_english_toggled"
+const val WAS_SHARED_THEME_EVER_ACTIVATED = "was_shared_theme_ever_activated"
+const val IS_USING_SHARED_THEME = "is_using_shared_theme"
+const val WAS_SHARED_THEME_FORCED = "was_shared_theme_forced"
+const val WAS_CUSTOM_THEME_SWITCH_DESCRIPTION_SHOWN = "was_custom_theme_switch_description_shown"
+const val WAS_SHARED_THEME_AFTER_UPDATE_CHECKED = "was_shared_theme_after_update_checked"
+const val SHOW_INFO_BUBBLE = "show_info_bubble"
+const val LAST_CONFLICT_RESOLUTION = "last_conflict_resolution"
+const val LAST_CONFLICT_APPLY_TO_ALL = "last_conflict_apply_to_all"
+const val AVOID_WHATS_NEW = "avoid_whats_new"
+const val HAD_THANK_YOU_INSTALLED = "had_thank_you_installed"
+const val SKIP_DELETE_CONFIRMATION = "skip_delete_confirmation"
+const val ENABLE_PULL_TO_REFRESH = "enable_pull_to_refresh"
+const val SCROLL_HORIZONTALLY = "scroll_horizontally"
+const val PREVENT_PHONE_FROM_SLEEPING = "prevent_phone_from_sleeping"
+const val LAST_USED_VIEW_PAGER_PAGE = "last_used_view_pager_page"
+const val USE_24_HOUR_FORMAT = "use_24_hour_format"
+const val SUNDAY_FIRST = "sunday_first"
+const val WAS_ALARM_WARNING_SHOWN = "was_alarm_warning_shown"
+const val WAS_REMINDER_WARNING_SHOWN = "was_reminder_warning_shown"
+const val USE_SAME_SNOOZE = "use_same_snooze"
+const val SNOOZE_TIME = "snooze_delay"
+const val VIBRATE_ON_BUTTON_PRESS = "vibrate_on_button_press"
+const val YOUR_ALARM_SOUNDS = "your_alarm_sounds"
+const val SILENT = "silent"
+const val FORCE_PORTRAIT = "force_portrait"
+const val OTG_PARTITION = "otg_partition"
+const val IS_USING_MODIFIED_APP_ICON = "is_using_modified_app_icon"
+const val INITIAL_WIDGET_HEIGHT = "initial_widget_height"
+const val WIDGET_ID_TO_MEASURE = "widget_id_to_measure"
+const val WAS_ORANGE_ICON_CHECKED = "was_orange_icon_checked"
+const val WAS_APP_ON_SD_SHOWN = "was_app_on_sd_shown"
+const val WAS_BEFORE_ASKING_SHOWN = "was_before_asking_shown"
+
+// licenses
+internal const val LICENSE_KOTLIN = 1
+const val LICENSE_SUBSAMPLING = 2
+const val LICENSE_GLIDE = 4
+const val LICENSE_CROPPER = 8
+const val LICENSE_MULTISELECT = 16
+const val LICENSE_RTL = 32
+const val LICENSE_JODA = 64
+const val LICENSE_STETHO = 128
+const val LICENSE_OTTO = 256
+const val LICENSE_PHOTOVIEW = 512
+const val LICENSE_PICASSO = 1024
+const val LICENSE_PATTERN = 2048
+const val LICENSE_REPRINT = 4096
+const val LICENSE_GIF_DRAWABLE = 8192
+const val LICENSE_AUTOFITTEXTVIEW = 16384
+const val LICENSE_ROBOLECTRIC = 32768
+const val LICENSE_ESPRESSO = 65536
+const val LICENSE_GSON = 131072
+const val LICENSE_LEAK_CANARY = 262144
+const val LICENSE_NUMBER_PICKER = 524288
+const val LICENSE_EXOPLAYER = 1048576
+const val LICENSE_PANORAMA_VIEW = 2097152
+const val LICENSE_SANSELAN = 4194304
+const val LICENSE_FILTERS = 8388608
+
+// global intents
+const val OPEN_DOCUMENT_TREE = 1000
+const val OPEN_DOCUMENT_TREE_OTG = 1001
+const val REQUEST_SET_AS = 1002
+const val REQUEST_EDIT_IMAGE = 1003
+
+// sorting
+const val SORT_ORDER = "sort_order"
+const val SORT_BY_NAME = 1
+const val SORT_BY_DATE_MODIFIED = 2
+const val SORT_BY_SIZE = 4
+const val SORT_BY_DATE_TAKEN = 8
+const val SORT_BY_EXTENSION = 16
+const val SORT_BY_PATH = 32
+const val SORT_BY_NUMBER = 64
+const val SORT_BY_FIRST_NAME = 128
+const val SORT_BY_MIDDLE_NAME = 256
+const val SORT_BY_SURNAME = 512
+const val SORT_DESCENDING = 1024
+const val SORT_BY_TITLE = 2048
+const val SORT_BY_ARTIST = 4096
+const val SORT_BY_DURATION = 8192
+
+// security
+const val WAS_PROTECTION_HANDLED = "was_protection_handled"
+const val PROTECTION_PATTERN = 0
+const val PROTECTION_PIN = 1
+const val PROTECTION_FINGERPRINT = 2
+
+const val SHOW_ALL_TABS = -1
+const val SHOW_PATTERN = 0
+const val SHOW_PIN = 1
+const val SHOW_FINGERPRINT = 2
+
+// permissions
+const val PERMISSION_READ_STORAGE = 1
+const val PERMISSION_WRITE_STORAGE = 2
+const val PERMISSION_CAMERA = 3
+const val PERMISSION_RECORD_AUDIO = 4
+const val PERMISSION_READ_CONTACTS = 5
+const val PERMISSION_WRITE_CONTACTS = 6
+const val PERMISSION_READ_CALENDAR = 7
+const val PERMISSION_WRITE_CALENDAR = 8
+const val PERMISSION_CALL_PHONE = 9
+const val PERMISSION_READ_CALL_LOG = 10
+const val PERMISSION_WRITE_CALL_LOG = 11
+const val PERMISSION_GET_ACCOUNTS = 12
+
+// conflict resolving
+const val CONFLICT_SKIP = 1
+const val CONFLICT_OVERWRITE = 2
+const val CONFLICT_MERGE = 3
+
+const val MONDAY_BIT = 1
+const val TUESDAY_BIT = 2
+const val WEDNESDAY_BIT = 4
+const val THURSDAY_BIT = 8
+const val FRIDAY_BIT = 16
+const val SATURDAY_BIT = 32
+const val SUNDAY_BIT = 64
+const val EVERY_DAY_BIT = MONDAY_BIT or TUESDAY_BIT or WEDNESDAY_BIT or THURSDAY_BIT or FRIDAY_BIT or SATURDAY_BIT or SUNDAY_BIT
+const val WEEK_DAYS_BIT = MONDAY_BIT or TUESDAY_BIT or WEDNESDAY_BIT or THURSDAY_BIT or FRIDAY_BIT
+const val WEEKENDS_BIT = SATURDAY_BIT or SUNDAY_BIT
+
+val photoExtensions: Array get() = arrayOf(".jpg", ".png", ".jpeg", ".bmp", ".webp")
+val videoExtensions: Array get() = arrayOf(".mp4", ".mkv", ".webm", ".avi", ".3gp", ".mov", ".m4v", ".3gpp")
+val audioExtensions: Array get() = arrayOf(".mp3", ".wav", ".wma", ".ogg", ".m4a", ".opus", ".flac", ".aac")
+val rawExtensions: Array get() = arrayOf(".dng", ".orf", ".nef")
+
+val appIconColorStrings = arrayListOf(
+ ".Red",
+ ".Pink",
+ ".Purple",
+ ".Deep_purple",
+ ".Indigo",
+ ".Blue",
+ ".Light_blue",
+ ".Cyan",
+ ".Teal",
+ ".Green",
+ ".Light_green",
+ ".Lime",
+ ".Yellow",
+ ".Amber",
+ ".Orange",
+ ".Deep_orange",
+ ".Brown",
+ ".Blue_grey",
+ ".Grey_black"
+)
+
+fun isOnMainThread() = Looper.myLooper() == Looper.getMainLooper()
+
+fun isJellyBean1Plus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
+fun isAndroidFour() = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH
+fun isKitkatPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+fun isLollipopPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
+fun isMarshmallowPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+fun isNougatPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
+fun isOreoPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+
+fun getDateFormats() = arrayListOf(
+ "yyyy-MM-dd",
+ "yyyyMMdd",
+ "yyyy.MM.dd",
+ "yy-MM-dd",
+ "yyMMdd",
+ "yy.MM.dd",
+ "yy/MM/dd",
+ "MM-dd",
+ "--MM-dd",
+ "MMdd",
+ "MM/dd",
+ "MM.dd"
+)
+
+val normalizeRegex = "\\p{InCombiningDiacriticalMarks}+".toRegex()
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/Inlines.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/Inlines.kt
new file mode 100644
index 0000000..2c117b3
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/Inlines.kt
@@ -0,0 +1,5 @@
+package com.simplemobiletools.commons.helpers
+
+inline fun Iterable.sumByLong(selector: (T) -> Long) = this.map { selector(it) }.sum()
+
+inline fun Iterable.sumByInt(selector: (T) -> Int) = this.map { selector(it) }.sum()
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/MyContentProvider.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/MyContentProvider.kt
new file mode 100644
index 0000000..c688afe
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/MyContentProvider.kt
@@ -0,0 +1,29 @@
+package com.simplemobiletools.commons.helpers
+
+import android.content.ContentValues
+import android.net.Uri
+import com.simplemobiletools.commons.models.SharedTheme
+
+class MyContentProvider {
+ companion object {
+ private const val AUTHORITY = "com.simplemobiletools.commons.provider"
+ const val SHARED_THEME_ACTIVATED = "com.simplemobiletools.commons.SHARED_THEME_ACTIVATED"
+ const val SHARED_THEME_UPDATED = "com.simplemobiletools.commons.SHARED_THEME_UPDATED"
+ val MY_CONTENT_URI = Uri.parse("content://$AUTHORITY/themes")
+
+ const val COL_ID = "_id" // used in Simple Thank You
+ const val COL_TEXT_COLOR = "text_color"
+ const val COL_BACKGROUND_COLOR = "background_color"
+ const val COL_PRIMARY_COLOR = "primary_color"
+ const val COL_APP_ICON_COLOR = "app_icon_color"
+ const val COL_LAST_UPDATED_TS = "last_updated_ts"
+
+ fun fillThemeContentValues(sharedTheme: SharedTheme) = ContentValues().apply {
+ put(COL_TEXT_COLOR, sharedTheme.textColor)
+ put(COL_BACKGROUND_COLOR, sharedTheme.backgroundColor)
+ put(COL_PRIMARY_COLOR, sharedTheme.primaryColor)
+ put(COL_APP_ICON_COLOR, sharedTheme.appIconColor)
+ put(COL_LAST_UPDATED_TS, System.currentTimeMillis() / 1000)
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/MyContextWrapper.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/MyContextWrapper.kt
new file mode 100644
index 0000000..8505d9a
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/helpers/MyContextWrapper.kt
@@ -0,0 +1,56 @@
+package com.simplemobiletools.commons.helpers
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.res.Configuration
+import android.os.Build
+import java.util.*
+
+// language forcing used at "Use english language", taken from https://stackoverflow.com/a/40704077/1967672
+class MyContextWrapper(context: Context) : ContextWrapper(context) {
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ fun wrap(context: Context, language: String): ContextWrapper {
+ var newContext = context
+ val config = newContext.resources.configuration
+ val sysLocale: Locale?
+
+ sysLocale = if (isNougatPlus()) {
+ getSystemLocale(config)
+ } else {
+ getSystemLocaleLegacy(config)
+ }
+
+ if (language != "" && sysLocale!!.language != language) {
+ val locale = Locale(language)
+ Locale.setDefault(locale)
+ if (isNougatPlus()) {
+ setSystemLocale(config, locale)
+ } else {
+ setSystemLocaleLegacy(config, locale)
+ }
+ }
+
+ if (isJellyBean1Plus()) {
+ newContext = newContext.createConfigurationContext(config)
+ } else {
+ newContext.resources.updateConfiguration(config, newContext.resources.displayMetrics)
+ }
+ return MyContextWrapper(newContext)
+ }
+
+ private fun getSystemLocaleLegacy(config: Configuration) = config.locale
+
+ @TargetApi(Build.VERSION_CODES.N)
+ private fun getSystemLocale(config: Configuration) = config.locales.get(0)
+
+ private fun setSystemLocaleLegacy(config: Configuration, locale: Locale) {
+ config.locale = locale
+ }
+
+ @TargetApi(Build.VERSION_CODES.N)
+ private fun setSystemLocale(config: Configuration, locale: Locale) {
+ config.setLocale(locale)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/CopyMoveListener.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/CopyMoveListener.kt
new file mode 100644
index 0000000..ad6f7ca
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/CopyMoveListener.kt
@@ -0,0 +1,7 @@
+package com.simplemobiletools.commons.interfaces
+
+interface CopyMoveListener {
+ fun copySucceeded(copyOnly: Boolean, copiedAll: Boolean, destinationPath: String)
+
+ fun copyFailed()
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/HashListener.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/HashListener.kt
new file mode 100644
index 0000000..652f2b5
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/HashListener.kt
@@ -0,0 +1,5 @@
+package com.simplemobiletools.commons.interfaces
+
+interface HashListener {
+ fun receivedHash(hash: String, type: Int)
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/LineColorPickerListener.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/LineColorPickerListener.kt
new file mode 100644
index 0000000..dee0ea7
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/LineColorPickerListener.kt
@@ -0,0 +1,5 @@
+package com.simplemobiletools.commons.interfaces
+
+interface LineColorPickerListener {
+ fun colorChanged(index: Int, color: Int)
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/MyAdapterListener.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/MyAdapterListener.kt
new file mode 100644
index 0000000..f3d1612
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/MyAdapterListener.kt
@@ -0,0 +1,11 @@
+package com.simplemobiletools.commons.interfaces
+
+import java.util.*
+
+interface MyAdapterListener {
+ fun toggleItemSelectionAdapter(select: Boolean, position: Int)
+
+ fun getSelectedPositions(): HashSet
+
+ fun itemLongClicked(position: Int)
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/RecyclerScrollCallback.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/RecyclerScrollCallback.kt
new file mode 100644
index 0000000..d1ab3a5
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/RecyclerScrollCallback.kt
@@ -0,0 +1,5 @@
+package com.simplemobiletools.commons.interfaces
+
+interface RecyclerScrollCallback {
+ fun onScrolled(scrollY: Int)
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/RefreshRecyclerViewListener.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/RefreshRecyclerViewListener.kt
new file mode 100644
index 0000000..3ba64a2
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/RefreshRecyclerViewListener.kt
@@ -0,0 +1,5 @@
+package com.simplemobiletools.commons.interfaces
+
+interface RefreshRecyclerViewListener {
+ fun refreshItems()
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/SecurityTab.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/SecurityTab.kt
new file mode 100644
index 0000000..fee4b8a
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/interfaces/SecurityTab.kt
@@ -0,0 +1,9 @@
+package com.simplemobiletools.commons.interfaces
+
+import com.simplemobiletools.commons.views.MyScrollView
+
+interface SecurityTab {
+ fun initTab(requiredHash: String, listener: HashListener, scrollView: MyScrollView)
+
+ fun visibilityChanged(isVisible: Boolean)
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/AlarmSound.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/AlarmSound.kt
new file mode 100644
index 0000000..6760631
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/AlarmSound.kt
@@ -0,0 +1,3 @@
+package com.simplemobiletools.commons.models
+
+data class AlarmSound(val id: Int, var title: String, var uri: String)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/FAQItem.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/FAQItem.kt
new file mode 100644
index 0000000..b0462e1
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/FAQItem.kt
@@ -0,0 +1,9 @@
+package com.simplemobiletools.commons.models
+
+import java.io.Serializable
+
+data class FAQItem(val title: Any, val text: Any) : Serializable {
+ companion object {
+ private const val serialVersionUID = -6553345863512345L
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/FileDirItem.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/FileDirItem.kt
new file mode 100644
index 0000000..1d57f02
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/FileDirItem.kt
@@ -0,0 +1,109 @@
+package com.simplemobiletools.commons.models
+
+import android.content.Context
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.*
+import java.io.File
+
+data class FileDirItem(val path: String, val name: String = "", var isDirectory: Boolean = false, var children: Int = 0, var size: Long = 0L) :
+ Comparable {
+ companion object {
+ var sorting: Int = 0
+ }
+
+ override fun compareTo(other: FileDirItem): Int {
+ return if (isDirectory && !other.isDirectory) {
+ -1
+ } else if (!isDirectory && other.isDirectory) {
+ 1
+ } else {
+ var result: Int
+ when {
+ sorting and SORT_BY_NAME != 0 -> result = name.toLowerCase().compareTo(other.name.toLowerCase())
+ sorting and SORT_BY_SIZE != 0 -> result = when {
+ size == other.size -> 0
+ size > other.size -> 1
+ else -> -1
+ }
+ sorting and SORT_BY_DATE_MODIFIED != 0 -> {
+ val file = File(path)
+ val otherFile = File(other.path)
+ result = when {
+ file.lastModified() == otherFile.lastModified() -> 0
+ file.lastModified() > otherFile.lastModified() -> 1
+ else -> -1
+ }
+ }
+ else -> {
+ result = getExtension().toLowerCase().compareTo(other.getExtension().toLowerCase())
+ }
+ }
+
+ if (sorting and SORT_DESCENDING != 0) {
+ result *= -1
+ }
+ result
+ }
+ }
+
+ fun getExtension() = if (isDirectory) name else path.substringAfterLast('.', "")
+
+ fun getBubbleText() = when {
+ sorting and SORT_BY_SIZE != 0 -> size.formatSize()
+ sorting and SORT_BY_DATE_MODIFIED != 0 -> File(path).lastModified().formatDate()
+ sorting and SORT_BY_EXTENSION != 0 -> getExtension().toLowerCase()
+ else -> name
+ }
+
+ fun getProperSize(context: Context, countHidden: Boolean): Long {
+ return if (path.startsWith(OTG_PATH)) {
+ context.getDocumentFile(path)?.getItemSize(countHidden) ?: 0
+ } else {
+ File(path).getProperSize(countHidden)
+ }
+ }
+
+ fun getProperFileCount(context: Context, countHidden: Boolean): Int {
+ return if (path.startsWith(OTG_PATH)) {
+ context.getDocumentFile(path)?.getFileCount(countHidden) ?: 0
+ } else {
+ File(path).getFileCount(countHidden)
+ }
+ }
+
+ fun getDirectChildrenCount(context: Context, countHiddenItems: Boolean): Int {
+ return if (path.startsWith(OTG_PATH)) {
+ context.getDocumentFile(path)?.listFiles()?.filter { if (countHiddenItems) true else !it.name!!.startsWith(".") }?.size ?: 0
+ } else {
+ File(path).getDirectChildrenCount(countHiddenItems)
+ }
+ }
+
+ fun getLastModified(context: Context): Long {
+ return if (path.startsWith(OTG_PATH)) {
+ context.getFastDocumentFile(path)?.lastModified() ?: 0L
+ } else {
+ File(path).lastModified()
+ }
+ }
+
+ fun getParentPath() = path.getParentPath()
+
+ fun getDuration() = path.getDuration()
+
+ fun getFileDurationSeconds() = path.getFileDurationSeconds()
+
+ fun getArtist() = path.getFileArtist()
+
+ fun getAlbum() = path.getFileAlbum()
+
+ fun getSongTitle() = path.getFileSongTitle()
+
+ fun getResolution() = path.getResolution()
+
+ fun getVideoResolution() = path.getVideoResolution()
+
+ fun getImageResolution() = path.getImageResolution()
+
+ fun getPublicUri(context: Context) = context.getDocumentFile(path)?.uri ?: ""
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/License.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/License.kt
new file mode 100644
index 0000000..4a25bd3
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/License.kt
@@ -0,0 +1,3 @@
+package com.simplemobiletools.commons.models
+
+data class License(val id: Int, val titleId: Int, val textId: Int, val urlId: Int)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/MyTheme.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/MyTheme.kt
new file mode 100644
index 0000000..272aca9
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/MyTheme.kt
@@ -0,0 +1,3 @@
+package com.simplemobiletools.commons.models
+
+data class MyTheme(val nameId: Int, val textColorId: Int, val backgroundColorId: Int, val primaryColorId: Int, val appIconColorId: Int)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/RadioItem.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/RadioItem.kt
new file mode 100644
index 0000000..9a5a648
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/RadioItem.kt
@@ -0,0 +1,3 @@
+package com.simplemobiletools.commons.models
+
+data class RadioItem(val id: Int, val title: String, val value: Any = id)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/Release.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/Release.kt
new file mode 100644
index 0000000..d3ad126
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/Release.kt
@@ -0,0 +1,3 @@
+package com.simplemobiletools.commons.models
+
+data class Release(val id: Int, val textId: Int)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/models/SharedTheme.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/models/SharedTheme.kt
new file mode 100644
index 0000000..2818fbd
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/models/SharedTheme.kt
@@ -0,0 +1,3 @@
+package com.simplemobiletools.commons.models
+
+data class SharedTheme(val textColor: Int, val backgroundColor: Int, val primaryColor: Int, val appIconColor: Int, val lastUpdatedTS: Int = 0)
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/receivers/SharedThemeReceiver.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/receivers/SharedThemeReceiver.kt
new file mode 100644
index 0000000..2f54eb8
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/receivers/SharedThemeReceiver.kt
@@ -0,0 +1,52 @@
+package com.simplemobiletools.commons.receivers
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.simplemobiletools.commons.extensions.baseConfig
+import com.simplemobiletools.commons.extensions.checkAppIconColor
+import com.simplemobiletools.commons.extensions.getSharedTheme
+import com.simplemobiletools.commons.helpers.MyContentProvider
+
+class SharedThemeReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ context.baseConfig.apply {
+ val oldColor = appIconColor
+ if (intent.action == MyContentProvider.SHARED_THEME_ACTIVATED) {
+ if (!wasSharedThemeForced) {
+ wasSharedThemeForced = true
+ isUsingSharedTheme = true
+ wasSharedThemeEverActivated = true
+
+ context.getSharedTheme {
+ if (it != null) {
+ textColor = it.textColor
+ backgroundColor = it.backgroundColor
+ primaryColor = it.primaryColor
+ appIconColor = it.appIconColor
+ checkAppIconColorChanged(oldColor, appIconColor, context)
+ }
+ }
+ }
+ } else if (intent.action == MyContentProvider.SHARED_THEME_UPDATED) {
+ if (isUsingSharedTheme) {
+ context.getSharedTheme {
+ if (it != null) {
+ textColor = it.textColor
+ backgroundColor = it.backgroundColor
+ primaryColor = it.primaryColor
+ appIconColor = it.appIconColor
+ checkAppIconColorChanged(oldColor, appIconColor, context)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun checkAppIconColorChanged(oldColor: Int, newColor: Int, context: Context) {
+ if (oldColor != newColor) {
+ context.checkAppIconColor()
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/Breadcrumbs.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/Breadcrumbs.kt
new file mode 100644
index 0000000..f5f5f30
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/Breadcrumbs.kt
@@ -0,0 +1,163 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.widget.LinearLayout
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.baseConfig
+import com.simplemobiletools.commons.extensions.getBasePath
+import com.simplemobiletools.commons.extensions.humanizePath
+import com.simplemobiletools.commons.extensions.onGlobalLayout
+import com.simplemobiletools.commons.models.FileDirItem
+import kotlinx.android.synthetic.main.breadcrumb_item.view.*
+
+class Breadcrumbs(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs), View.OnClickListener {
+ private var availableWidth = 0
+ private var inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ private var textColor = context.baseConfig.textColor
+ private var lastPath = ""
+
+ var listener: BreadcrumbsListener? = null
+
+ init {
+ onGlobalLayout {
+ availableWidth = width
+ }
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+ val childRight = measuredWidth - paddingRight
+ val childBottom = measuredHeight - paddingBottom
+ val childHeight = childBottom - paddingTop
+
+ val usableWidth = availableWidth - paddingLeft - paddingRight
+ var maxHeight = 0
+ var curWidth: Int
+ var curHeight: Int
+ var curLeft = paddingLeft
+ var curTop = paddingTop
+
+ val cnt = childCount
+ for (i in 0 until cnt) {
+ val child = getChildAt(i)
+
+ child.measure(MeasureSpec.makeMeasureSpec(usableWidth, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.AT_MOST))
+ curWidth = child.measuredWidth
+ curHeight = child.measuredHeight
+
+ if (curLeft + curWidth >= childRight) {
+ curLeft = paddingLeft
+ curTop += maxHeight
+ maxHeight = 0
+ }
+
+ child.layout(curLeft, curTop, curLeft + curWidth, curTop + curHeight)
+ if (maxHeight < curHeight)
+ maxHeight = curHeight
+
+ curLeft += curWidth
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val usableWidth = availableWidth - paddingLeft - paddingRight
+ var width = 0
+ var rowHeight = 0
+ var lines = 1
+
+ val cnt = childCount
+ for (i in 0 until cnt) {
+ val child = getChildAt(i)
+ measureChild(child, widthMeasureSpec, heightMeasureSpec)
+ width += child.measuredWidth
+ rowHeight = child.measuredHeight
+
+ if (width / usableWidth > 0) {
+ lines++
+ width = child.measuredWidth
+ }
+ }
+
+ val parentWidth = MeasureSpec.getSize(widthMeasureSpec)
+ val calculatedHeight = paddingTop + paddingBottom + rowHeight * lines
+ setMeasuredDimension(parentWidth, calculatedHeight)
+ }
+
+ fun setBreadcrumb(fullPath: String) {
+ lastPath = fullPath
+ val basePath = fullPath.getBasePath(context)
+ var currPath = basePath
+ val tempPath = context.humanizePath(fullPath)
+
+ removeAllViewsInLayout()
+ val dirs = tempPath.split("/").dropLastWhile(String::isEmpty)
+ for (i in dirs.indices) {
+ val dir = dirs[i]
+ if (i > 0) {
+ currPath += dir + "/"
+ }
+
+ if (dir.isEmpty()) {
+ continue
+ }
+
+ currPath = "${currPath.trimEnd('/')}/"
+ val item = FileDirItem(currPath, dir, true, 0, 0)
+ addBreadcrumb(item, i > 0)
+ }
+ }
+
+ private fun addBreadcrumb(item: FileDirItem, addPrefix: Boolean) {
+ inflater.inflate(R.layout.breadcrumb_item, null, false).apply {
+ var textToAdd = item.name
+ if (addPrefix) {
+ textToAdd = "/ $textToAdd"
+ }
+
+ if (childCount == 0) {
+ resources.apply {
+ background = getDrawable(R.drawable.button_background)
+ background.colorFilter = PorterDuffColorFilter(textColor, PorterDuff.Mode.SRC_IN)
+ val medium = getDimension(R.dimen.medium_margin).toInt()
+ setPadding(medium, medium, medium, medium)
+ }
+ }
+
+ breadcrumb_text.text = textToAdd
+ breadcrumb_text.setTextColor(textColor)
+ addView(this)
+ setOnClickListener(this@Breadcrumbs)
+
+ tag = item
+ }
+ }
+
+ fun updateColor(color: Int) {
+ textColor = color
+ setBreadcrumb(lastPath)
+ }
+
+ fun removeBreadcrumb() {
+ removeView(getChildAt(childCount - 1))
+ }
+
+ fun getLastItem() = getChildAt(childCount - 1).tag as FileDirItem
+
+ override fun onClick(v: View) {
+ val cnt = childCount
+ for (i in 0 until cnt) {
+ if (getChildAt(i) != null && getChildAt(i) == v) {
+ listener?.breadcrumbClicked(i)
+ }
+ }
+ }
+
+ interface BreadcrumbsListener {
+ fun breadcrumbClicked(id: Int)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/ColorPickerSquare.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/ColorPickerSquare.kt
new file mode 100644
index 0000000..26a0890
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/ColorPickerSquare.kt
@@ -0,0 +1,33 @@
+package com.simplemobiletools.commons.views
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.graphics.*
+import android.graphics.Shader.TileMode
+import android.util.AttributeSet
+import android.view.View
+
+class ColorPickerSquare(context: Context, attrs: AttributeSet) : View(context, attrs) {
+ var paint: Paint? = null
+ var luar: Shader = LinearGradient(0f, 0f, 0f, measuredHeight.toFloat(), Color.WHITE, Color.BLACK, Shader.TileMode.CLAMP)
+ val color = floatArrayOf(1f, 1f, 1f)
+
+ @SuppressLint("DrawAllocation")
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ if (paint == null) {
+ paint = Paint()
+ luar = LinearGradient(0f, 0f, 0f, measuredHeight.toFloat(), Color.WHITE, Color.BLACK, TileMode.CLAMP)
+ }
+ val rgb = Color.HSVToColor(color)
+ val dalam = LinearGradient(0f, 0f, measuredWidth.toFloat(), 0f, Color.WHITE, rgb, TileMode.CLAMP)
+ val shader = ComposeShader(luar, dalam, PorterDuff.Mode.MULTIPLY)
+ paint!!.shader = shader
+ canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), paint)
+ }
+
+ fun setHue(hue: Float) {
+ color[0] = hue
+ invalidate()
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/FastScroller.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/FastScroller.kt
new file mode 100644
index 0000000..d56c721
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/FastScroller.kt
@@ -0,0 +1,392 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.graphics.drawable.GradientDrawable
+import android.os.Handler
+import android.support.v4.widget.SwipeRefreshLayout
+import android.support.v7.widget.GridLayoutManager
+import android.support.v7.widget.RecyclerView
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+import android.widget.FrameLayout
+import android.widget.TextView
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.applyColorFilter
+import com.simplemobiletools.commons.extensions.baseConfig
+import com.simplemobiletools.commons.extensions.getAdjustedPrimaryColor
+import com.simplemobiletools.commons.extensions.onGlobalLayout
+
+// based on https://blog.stylingandroid.com/recyclerview-fastscroll-part-1
+class FastScroller : FrameLayout {
+ var isHorizontal = false
+ var allowBubbleDisplay = false
+ var measureItemIndex = 0
+
+ private var handle: View? = null
+ private var bubble: TextView? = null
+ private var recyclerViewWidth = 0
+ private var recyclerViewHeight = 0
+ private var currScrollX = 0
+ private var currScrollY = 0
+ private var handleWidth = 0
+ private var handleHeight = 0
+ private var bubbleHeight = 0
+ private var handleXOffset = 0
+ private var handleYOffset = 0
+ private var recyclerViewContentWidth = 1
+ private var recyclerViewContentHeight = 1
+ private var tinyMargin = 0
+ private var isScrollingEnabled = false // a boolean indicating whether the actual recycler view content is higher than the screen
+ private var fastScrollCallback: ((Int) -> Unit)? = null
+ private var wasRecyclerViewContentSizeSet = false // stop measuring and calculating content size as soon as it is manually set once
+
+ private val HANDLE_HIDE_DELAY = 1000L
+ private var recyclerView: RecyclerView? = null
+ private var swipeRefreshLayout: SwipeRefreshLayout? = null
+ private var bubbleHideHandler = Handler()
+ private var handleHideHandler = Handler()
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ fun setViews(recyclerView: RecyclerView, swipeRefreshLayout: SwipeRefreshLayout? = null, callback: ((Int) -> Unit)? = null) {
+ this.recyclerView = recyclerView
+ this.swipeRefreshLayout = swipeRefreshLayout
+ tinyMargin = context.resources.getDimension(R.dimen.tiny_margin).toInt()
+
+ updatePrimaryColor()
+ recyclerView.setOnScrollListener(object : RecyclerView.OnScrollListener() {
+ override fun onScrolled(rv: RecyclerView, dx: Int, dy: Int) {
+ if (isScrollingEnabled) {
+ if (!handle!!.isSelected) {
+ bubble?.alpha = 0f
+ bubble?.text = ""
+ bubbleHideHandler.removeCallbacksAndMessages(null)
+ }
+
+ currScrollX += dx
+ currScrollY += dy
+
+ currScrollX = getValueInRange(0, recyclerViewContentWidth, currScrollX.toFloat()).toInt()
+ currScrollY = getValueInRange(0, recyclerViewContentHeight, currScrollY.toFloat()).toInt()
+
+ updateHandlePosition()
+ }
+ }
+
+ override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
+ super.onScrollStateChanged(recyclerView, newState)
+ if (!isScrollingEnabled) {
+ hideHandle()
+ return
+ }
+
+ if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
+ showHandle()
+ } else if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ hideHandle()
+ }
+ }
+ })
+
+ fastScrollCallback = callback
+ measureRecyclerViewOnRedraw()
+ }
+
+ fun resetScrollPositions() {
+ currScrollX = 0
+ currScrollY = 0
+ }
+
+ fun measureRecyclerViewOnRedraw() {
+ recyclerView?.onGlobalLayout {
+ measureRecyclerView()
+ }
+ }
+
+ fun measureRecyclerView() {
+ if (recyclerView == null) {
+ return
+ }
+
+ if (!wasRecyclerViewContentSizeSet) {
+ val adapter = recyclerView!!.adapter
+ val spanCount = ((recyclerView!!.layoutManager as? GridLayoutManager)?.spanCount ?: 1)
+ val otherDimension = Math.floor((adapter.itemCount - 1) / spanCount.toDouble()) + 1
+ val size = recyclerView!!.getChildAt(measureItemIndex)?.height ?: 0
+ if (isHorizontal) {
+ recyclerViewContentWidth = (otherDimension * size).toInt()
+ } else {
+ recyclerViewContentHeight = (otherDimension * size).toInt()
+ }
+ }
+
+ isScrollingEnabled = if (isHorizontal) {
+ recyclerViewContentWidth > recyclerViewWidth
+ } else {
+ recyclerViewContentHeight > recyclerViewHeight
+ }
+
+ if (!isScrollingEnabled) {
+ bubbleHideHandler.removeCallbacksAndMessages(null)
+ bubble?.animate()?.cancel()
+ bubble?.alpha = 0f
+ bubble?.text = ""
+
+ handleHideHandler.removeCallbacksAndMessages(null)
+ handle?.animate()?.cancel()
+ handle?.alpha = 0f
+ }
+ }
+
+ fun setContentWidth(width: Int) {
+ recyclerViewContentWidth = width
+ wasRecyclerViewContentSizeSet = true
+ updateHandlePosition()
+ isScrollingEnabled = recyclerViewContentWidth > recyclerViewWidth
+ }
+
+ fun setContentHeight(height: Int) {
+ recyclerViewContentHeight = height
+ wasRecyclerViewContentSizeSet = true
+ updateHandlePosition()
+ isScrollingEnabled = recyclerViewContentHeight > recyclerViewHeight
+ }
+
+ fun setScrollToX(x: Int) {
+ measureRecyclerView()
+ currScrollX = x
+ updateHandlePosition()
+ hideHandle()
+ }
+
+ fun setScrollToY(y: Int) {
+ measureRecyclerView()
+ currScrollY = y
+ updateHandlePosition()
+ hideHandle()
+ }
+
+ fun updatePrimaryColor() {
+ handle!!.background.applyColorFilter(context.getAdjustedPrimaryColor())
+ updateBubblePrimaryColor()
+ }
+
+ fun updateBubbleColors() {
+ updateBubblePrimaryColor()
+ updateBubbleTextColor()
+ updateBubbleBackgroundColor()
+ }
+
+ fun updateBubblePrimaryColor() {
+ getBubbleBackgroundDrawable()?.setStroke(resources.displayMetrics.density.toInt(), context.getAdjustedPrimaryColor())
+ }
+
+ fun updateBubbleTextColor() {
+ bubble?.setTextColor(context.baseConfig.textColor)
+ }
+
+ fun updateBubbleBackgroundColor() {
+ getBubbleBackgroundDrawable()?.setColor(context.baseConfig.backgroundColor)
+ }
+
+ fun updateBubbleText(text: String) {
+ bubble?.text = text
+ }
+
+ private fun getBubbleBackgroundDrawable() = bubble?.background as? GradientDrawable
+
+ override fun onSizeChanged(width: Int, height: Int, oldWidth: Int, oldHeight: Int) {
+ super.onSizeChanged(width, height, oldWidth, oldHeight)
+ recyclerViewWidth = width
+ recyclerViewHeight = height
+ }
+
+ private fun updateHandlePosition() {
+ if (handle!!.isSelected || recyclerView == null)
+ return
+
+ if (isHorizontal) {
+ val proportion = currScrollX.toFloat() / (recyclerViewContentWidth - recyclerViewWidth)
+ val targetX = proportion * (recyclerViewWidth - handleWidth)
+ handle!!.x = getValueInRange(0, recyclerViewWidth - handleWidth, targetX)
+ } else {
+ val proportion = currScrollY.toFloat() / (recyclerViewContentHeight - recyclerViewHeight)
+ val targetY = proportion * (recyclerViewHeight - handleHeight)
+ handle!!.y = getValueInRange(0, recyclerViewHeight - handleHeight, targetY)
+ }
+ showHandle()
+ }
+
+ override fun onTouchEvent(event: MotionEvent): Boolean {
+ // allow dragging only the handle itself
+ if (!isScrollingEnabled) {
+ return super.onTouchEvent(event)
+ }
+
+ if (!handle!!.isSelected) {
+ if (isHorizontal) {
+ val min = handle!!.x
+ val max = min + handleWidth
+ if (event.x < min || event.x > max) {
+ return super.onTouchEvent(event)
+ }
+ } else {
+ val min = handle!!.y
+ val max = min + handleHeight
+ if (event.y < min || event.y > max) {
+ return super.onTouchEvent(event)
+ }
+ }
+ }
+
+ return when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ if (isHorizontal) {
+ handleXOffset = (event.x - handle!!.x).toInt()
+ } else {
+ handleYOffset = (event.y - handle!!.y).toInt()
+ }
+ if (isScrollingEnabled) {
+ startScrolling()
+ }
+ true
+ }
+ MotionEvent.ACTION_MOVE -> {
+ if (isScrollingEnabled) {
+ try {
+ if (isHorizontal) {
+ setPosition(event.x)
+ setRecyclerViewPosition(event.x)
+ } else {
+ setPosition(event.y)
+ setRecyclerViewPosition(event.y)
+ }
+ } catch (ignored: Exception) {
+ }
+ }
+ true
+ }
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ handleYOffset = 0
+ handle!!.isSelected = false
+ if (context.baseConfig.enablePullToRefresh) {
+ swipeRefreshLayout?.isEnabled = true
+ }
+ hideHandle()
+ true
+ }
+ else -> super.onTouchEvent(event)
+ }
+ }
+
+ private fun startScrolling() {
+ handle!!.isSelected = true
+ swipeRefreshLayout?.isEnabled = false
+ showHandle()
+ }
+
+ private fun setRecyclerViewPosition(pos: Float) {
+ if (recyclerView != null) {
+ val targetProportion: Float
+ if (isHorizontal) {
+ targetProportion = currScrollX / recyclerViewContentWidth.toFloat()
+ val diffInMove = pos - handleXOffset
+ val movePercent = diffInMove / (recyclerViewWidth.toFloat() - handleWidth)
+ val target = (recyclerViewContentWidth - recyclerViewWidth) * movePercent
+ val diff = target.toInt() - currScrollX
+ recyclerView!!.scrollBy(diff, 0)
+ } else {
+ targetProportion = currScrollY / recyclerViewContentHeight.toFloat()
+ val diffInMove = pos - handleYOffset
+ val movePercent = diffInMove / (recyclerViewHeight.toFloat() - handleHeight)
+ val target = (recyclerViewContentHeight - recyclerViewHeight) * movePercent
+ val diff = target.toInt() - currScrollY
+ recyclerView!!.scrollBy(0, diff)
+ }
+
+ val itemCount = recyclerView!!.adapter.itemCount
+ val targetPos = getValueInRange(0, itemCount - 1, targetProportion * itemCount).toInt()
+ fastScrollCallback?.invoke(targetPos)
+ }
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ handle = getChildAt(0)
+ handle!!.onGlobalLayout {
+ handleWidth = handle!!.width
+ handleHeight = handle!!.height
+ showHandle()
+ hideHandle()
+ }
+
+ bubble = getChildAt(1) as? TextView
+ bubble?.onGlobalLayout {
+ if (bubbleHeight == 0) {
+ bubbleHeight = bubble!!.height
+ }
+ updateBubbleColors()
+ }
+ }
+
+ private fun showHandle() {
+ if (!isScrollingEnabled) {
+ return
+ }
+
+ handleHideHandler.removeCallbacksAndMessages(null)
+ handle!!.animate().cancel()
+ handle!!.alpha = 1f
+ if (handleWidth == 0 && handleHeight == 0) {
+ handleWidth = handle!!.width
+ handleHeight = handle!!.height
+ }
+ }
+
+ private fun hideHandle() {
+ if (!handle!!.isSelected) {
+ handleHideHandler.removeCallbacksAndMessages(null)
+ handleHideHandler.postDelayed({
+ handle!!.animate().alpha(0f).start()
+ }, HANDLE_HIDE_DELAY)
+
+ if (bubble != null) {
+ bubbleHideHandler.removeCallbacksAndMessages(null)
+ bubbleHideHandler.postDelayed({
+ bubble?.animate()?.alpha(0f)?.withEndAction {
+ if (bubble?.alpha == 0f) {
+ bubble?.text = ""
+ }
+ }
+ }, HANDLE_HIDE_DELAY)
+ }
+ }
+ }
+
+ private fun setPosition(pos: Float) {
+ if (isHorizontal) {
+ handle!!.x = getValueInRange(0, recyclerViewWidth - handleWidth, pos - handleXOffset)
+ if (bubble != null && allowBubbleDisplay && handle!!.isSelected) {
+ val bubbleWidth = bubble!!.width
+ bubble!!.x = getValueInRange(tinyMargin, recyclerViewWidth - bubbleWidth, handle!!.x - bubbleWidth)
+ bubbleHideHandler.removeCallbacksAndMessages(null)
+ bubble?.alpha = 1f
+ }
+ } else {
+ handle!!.y = getValueInRange(0, recyclerViewHeight - handleHeight, pos - handleYOffset)
+ if (bubble != null && allowBubbleDisplay && handle!!.isSelected) {
+ bubble!!.y = getValueInRange(tinyMargin.toInt(), recyclerViewHeight - bubbleHeight, handle!!.y - bubbleHeight)
+ bubbleHideHandler.removeCallbacksAndMessages(null)
+ bubble?.alpha = 1f
+ }
+ }
+ hideHandle()
+ }
+
+ private fun getValueInRange(min: Int, max: Int, value: Float) = Math.min(Math.max(min.toFloat(), value), max.toFloat())
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/LineColorPicker.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/LineColorPicker.kt
new file mode 100644
index 0000000..1283914
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/LineColorPicker.kt
@@ -0,0 +1,107 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.widget.LinearLayout
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.isRTLLayout
+import com.simplemobiletools.commons.extensions.onGlobalLayout
+import com.simplemobiletools.commons.interfaces.LineColorPickerListener
+import java.util.*
+
+class LineColorPicker(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
+ private var colorsCount = 0
+ private var pickerWidth = 0
+ private var stripeWidth = 0
+ private var unselectedMargin = 0
+ private var lastColorIndex = -1
+ private var wasInit = false
+ private var colors = ArrayList()
+
+ var listener: LineColorPickerListener? = null
+
+ init {
+ unselectedMargin = context.resources.getDimension(R.dimen.line_color_picker_margin).toInt()
+ onGlobalLayout {
+ if (pickerWidth == 0) {
+ pickerWidth = width
+
+ if (colorsCount != 0)
+ stripeWidth = width / colorsCount
+ }
+
+ if (!wasInit) {
+ wasInit = true
+ initColorPicker()
+ updateItemMargin(lastColorIndex, false)
+ }
+ }
+ orientation = LinearLayout.HORIZONTAL
+
+ setOnTouchListener { view, motionEvent ->
+ when (motionEvent.action) {
+ MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP -> {
+ if (pickerWidth != 0 && stripeWidth != 0) {
+ touchAt(motionEvent.x.toInt())
+ }
+ }
+ }
+ true
+ }
+ }
+
+ fun updateColors(colors: ArrayList, selectColorIndex: Int = -1) {
+ this.colors = colors
+ colorsCount = colors.size
+ if (pickerWidth != 0) {
+ stripeWidth = pickerWidth / colorsCount
+ }
+
+ if (selectColorIndex != -1) {
+ lastColorIndex = selectColorIndex
+ }
+
+ initColorPicker()
+ updateItemMargin(lastColorIndex, false)
+ }
+
+ // do not remove ": Int", it causes "NoSuchMethodError" for some reason
+ fun getCurrentColor(): Int = colors[lastColorIndex]
+
+ private fun initColorPicker() {
+ removeAllViews()
+ val inflater = LayoutInflater.from(context)
+ colors.forEach {
+ inflater.inflate(R.layout.empty_image_view, this, false).apply {
+ setBackgroundColor(it)
+ addView(this)
+ }
+ }
+ }
+
+ private fun touchAt(touchX: Int) {
+ var colorIndex = touchX / stripeWidth
+ if (context.isRTLLayout) {
+ colorIndex = colors.size - colorIndex - 1
+ }
+ val index = Math.max(0, Math.min(colorIndex, colorsCount - 1))
+ if (lastColorIndex != index) {
+ updateItemMargin(lastColorIndex, true)
+ lastColorIndex = index
+ updateItemMargin(index, false)
+ listener?.colorChanged(index, colors[index])
+ }
+ }
+
+ private fun updateItemMargin(index: Int, addMargin: Boolean) {
+ getChildAt(index)?.apply {
+ (layoutParams as LinearLayout.LayoutParams).apply {
+ topMargin = if (addMargin) unselectedMargin else 0
+ bottomMargin = if (addMargin) unselectedMargin else 0
+ }
+ requestLayout()
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyAppCompatCheckbox.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyAppCompatCheckbox.kt
new file mode 100644
index 0000000..c6259d5
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyAppCompatCheckbox.kt
@@ -0,0 +1,26 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.support.v7.widget.AppCompatCheckBox
+import android.util.AttributeSet
+import com.simplemobiletools.commons.R
+
+class MyAppCompatCheckbox : AppCompatCheckBox {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) {
+ setTextColor(textColor)
+ val colorStateList = ColorStateList(
+ arrayOf(intArrayOf(-android.R.attr.state_checked),
+ intArrayOf(android.R.attr.state_checked)
+ ),
+ intArrayOf(context.resources.getColor(R.color.radiobutton_disabled), accentColor)
+ )
+ supportButtonTintList = colorStateList
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyAppCompatSpinner.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyAppCompatSpinner.kt
new file mode 100644
index 0000000..2017e92
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyAppCompatSpinner.kt
@@ -0,0 +1,48 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.support.v7.widget.AppCompatSpinner
+import android.util.AttributeSet
+import android.view.View
+import android.widget.AdapterView
+import android.widget.TextView
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.adapters.MyArrayAdapter
+import com.simplemobiletools.commons.extensions.applyColorFilter
+
+class MyAppCompatSpinner : AppCompatSpinner {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) {
+ if (adapter == null)
+ return
+
+ val cnt = adapter.count
+ val items = kotlin.arrayOfNulls(cnt)
+ for (i in 0..cnt - 1)
+ items[i] = adapter.getItem(i)
+
+ val position = selectedItemPosition
+ val padding = resources.getDimension(R.dimen.activity_margin).toInt()
+ adapter = MyArrayAdapter(context, android.R.layout.simple_spinner_item, items, textColor, backgroundColor, padding)
+ setSelection(position)
+
+ val superListener = onItemSelectedListener
+ onItemSelectedListener = object : OnItemSelectedListener {
+ override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
+ if (view != null) {
+ (view as TextView).setTextColor(textColor)
+ }
+ superListener?.onItemSelected(parent, view, position, id)
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {
+ }
+ }
+ background.applyColorFilter(textColor)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyButton.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyButton.kt
new file mode 100644
index 0000000..070092a
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyButton.kt
@@ -0,0 +1,17 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.Button
+
+class MyButton : Button {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) {
+ setTextColor(textColor)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyCompatRadioButton.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyCompatRadioButton.kt
new file mode 100644
index 0000000..acf3ac3
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyCompatRadioButton.kt
@@ -0,0 +1,28 @@
+package com.simplemobiletools.commons.views
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.ColorStateList
+import android.support.v7.widget.AppCompatRadioButton
+import android.util.AttributeSet
+import com.simplemobiletools.commons.R
+
+class MyCompatRadioButton : AppCompatRadioButton {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ @SuppressLint("RestrictedApi")
+ fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) {
+ setTextColor(textColor)
+ val colorStateList = ColorStateList(
+ arrayOf(intArrayOf(-android.R.attr.state_checked),
+ intArrayOf(android.R.attr.state_checked)
+ ),
+ intArrayOf(context.resources.getColor(R.color.radiobutton_disabled), accentColor)
+ )
+ supportButtonTintList = colorStateList
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyDialogViewPager.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyDialogViewPager.kt
new file mode 100644
index 0000000..978a064
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyDialogViewPager.kt
@@ -0,0 +1,44 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.support.v4.view.ViewPager
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.View
+
+class MyDialogViewPager : ViewPager {
+ var allowSwiping = true
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ // disable manual swiping of viewpager at the dialog by swiping over the pattern
+ override fun onInterceptTouchEvent(ev: MotionEvent) = false
+
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ if (!allowSwiping)
+ return false
+
+ try {
+ return super.onTouchEvent(ev)
+ } catch (ignored: Exception) {
+ }
+
+ return false
+ }
+
+ // https://stackoverflow.com/a/20784791/1967672
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ var height = 0
+ for (i in 0 until childCount) {
+ val child = getChildAt(i)
+ child.measure(widthMeasureSpec, View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
+ val h = child.measuredHeight
+ if (h > height) height = h
+ }
+
+ val newHeightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
+ super.onMeasure(widthMeasureSpec, newHeightMeasureSpec)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyEditText.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyEditText.kt
new file mode 100644
index 0000000..9189ea7
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyEditText.kt
@@ -0,0 +1,24 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.EditText
+import com.simplemobiletools.commons.extensions.adjustAlpha
+import com.simplemobiletools.commons.extensions.applyColorFilter
+
+class MyEditText : EditText {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) {
+ background?.mutate()?.applyColorFilter(accentColor)
+
+ // requires android:textCursorDrawable="@null" in xml to color the cursor too
+ setTextColor(textColor)
+ setHintTextColor(textColor.adjustAlpha(0.5f))
+ setLinkTextColor(accentColor)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyLinearLayoutManager.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyLinearLayoutManager.kt
new file mode 100644
index 0000000..915f35b
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyLinearLayoutManager.kt
@@ -0,0 +1,16 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.support.v7.widget.LinearLayoutManager
+import android.util.AttributeSet
+
+class MyLinearLayoutManager : LinearLayoutManager {
+ constructor(context: Context) : super(context)
+
+
+ constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
+
+ // fixes crash java.lang.IndexOutOfBoundsException: Inconsistency detected...
+ // taken from https://stackoverflow.com/a/33985508/1967672
+ override fun supportsPredictiveItemAnimations() = false
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyRecyclerView.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyRecyclerView.kt
new file mode 100644
index 0000000..53d456e
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyRecyclerView.kt
@@ -0,0 +1,333 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.os.Handler
+import android.support.v7.widget.LinearLayoutManager
+import android.support.v7.widget.RecyclerView
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.view.ScaleGestureDetector
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.interfaces.RecyclerScrollCallback
+
+// drag selection is based on https://github.com/afollestad/drag-select-recyclerview
+open class MyRecyclerView : RecyclerView {
+ private val AUTO_SCROLL_DELAY = 25L
+ private var isZoomEnabled = false
+ private var isDragSelectionEnabled = false
+ private var zoomListener: MyZoomListener? = null
+ private var dragListener: MyDragListener? = null
+ private var autoScrollHandler = Handler()
+
+ private var scaleDetector: ScaleGestureDetector
+
+ private var dragSelectActive = false
+ private var lastDraggedIndex = -1
+ private var minReached = 0
+ private var maxReached = 0
+ private var initialSelection = 0
+
+ private var hotspotHeight = 0
+ private var hotspotOffsetTop = 0
+ private var hotspotOffsetBottom = 0
+
+ private var hotspotTopBoundStart = 0
+ private var hotspotTopBoundEnd = 0
+ private var hotspotBottomBoundStart = 0
+ private var hotspotBottomBoundEnd = 0
+ private var autoScrollVelocity = 0
+
+ private var inTopHotspot = false
+ private var inBottomHotspot = false
+
+ private var currScaleFactor = 1.0f
+ private var lastUp = 0L // allow only pinch zoom, not double tap
+
+ // things related to parallax scrolling (for now only in the music player)
+ // cut from https://github.com/ksoichiro/Android-ObservableScrollView
+ var recyclerScrollCallback: RecyclerScrollCallback? = null
+ private var mPrevFirstVisiblePosition = 0
+ private var mPrevScrolledChildrenHeight = 0
+ private var mPrevFirstVisibleChildHeight = -1
+ private var mScrollY = 0
+
+ // variables used for fetching additional items at scrolling to the bottom/top
+ var endlessScrollListener: EndlessScrollListener? = null
+ private var totalItemCount = 0
+ private var lastMaxItemIndex = 0
+ private var linearLayoutManager: LinearLayoutManager? = null
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ init {
+ hotspotHeight = context.resources.getDimensionPixelSize(R.dimen.dragselect_hotspot_height)
+
+ if (layoutManager is LinearLayoutManager) {
+ linearLayoutManager = layoutManager as LinearLayoutManager
+ }
+
+ val gestureListener = object : MyGestureListener {
+ override fun getLastUp() = lastUp
+
+ override fun getScaleFactor() = currScaleFactor
+
+ override fun setScaleFactor(value: Float) {
+ currScaleFactor = value
+ }
+
+ override fun getZoomListener() = zoomListener
+ }
+
+ scaleDetector = ScaleGestureDetector(context, GestureListener(gestureListener))
+ }
+
+ override fun onMeasure(widthSpec: Int, heightSpec: Int) {
+ super.onMeasure(widthSpec, heightSpec)
+ if (hotspotHeight > -1) {
+ hotspotTopBoundStart = hotspotOffsetTop
+ hotspotTopBoundEnd = hotspotOffsetTop + hotspotHeight
+ hotspotBottomBoundStart = measuredHeight - hotspotHeight - hotspotOffsetBottom
+ hotspotBottomBoundEnd = measuredHeight - hotspotOffsetBottom
+ }
+ }
+
+ private val autoScrollRunnable = object : Runnable {
+ override fun run() {
+ if (inTopHotspot) {
+ scrollBy(0, -autoScrollVelocity)
+ autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY)
+ } else if (inBottomHotspot) {
+ scrollBy(0, autoScrollVelocity)
+ autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY)
+ }
+ }
+ }
+
+ fun resetItemCount() {
+ totalItemCount = 0
+ }
+
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ if (!dragSelectActive) {
+ try {
+ super.dispatchTouchEvent(ev)
+ } catch (ignored: Exception) {
+ }
+ }
+
+ when (ev.action) {
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
+ dragSelectActive = false
+ inTopHotspot = false
+ inBottomHotspot = false
+ autoScrollHandler.removeCallbacks(autoScrollRunnable)
+ currScaleFactor = 1.0f
+ lastUp = System.currentTimeMillis()
+ return true
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ if (dragSelectActive) {
+ val itemPosition = getItemPosition(ev)
+ if (hotspotHeight > -1) {
+ if (ev.y in hotspotTopBoundStart..hotspotTopBoundEnd) {
+ inBottomHotspot = false
+ if (!inTopHotspot) {
+ inTopHotspot = true
+ autoScrollHandler.removeCallbacks(autoScrollRunnable)
+ autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY)
+ }
+
+ val simulatedFactor = (hotspotTopBoundEnd - hotspotTopBoundStart).toFloat()
+ val simulatedY = ev.y - hotspotTopBoundStart
+ autoScrollVelocity = (simulatedFactor - simulatedY).toInt() / 2
+ } else if (ev.y in hotspotBottomBoundStart..hotspotBottomBoundEnd) {
+ inTopHotspot = false
+ if (!inBottomHotspot) {
+ inBottomHotspot = true
+ autoScrollHandler.removeCallbacks(autoScrollRunnable)
+ autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY)
+ }
+
+ val simulatedY = ev.y + hotspotBottomBoundEnd
+ val simulatedFactor = (hotspotBottomBoundStart + hotspotBottomBoundEnd).toFloat()
+ autoScrollVelocity = (simulatedY - simulatedFactor).toInt() / 2
+ } else if (inTopHotspot || inBottomHotspot) {
+ autoScrollHandler.removeCallbacks(autoScrollRunnable)
+ inTopHotspot = false
+ inBottomHotspot = false
+ }
+ }
+
+ if (itemPosition != RecyclerView.NO_POSITION && lastDraggedIndex != itemPosition) {
+ lastDraggedIndex = itemPosition
+ if (minReached == -1) {
+ minReached = lastDraggedIndex
+ }
+
+ if (maxReached == -1) {
+ maxReached = lastDraggedIndex
+ }
+
+ if (lastDraggedIndex > maxReached) {
+ maxReached = lastDraggedIndex
+ }
+
+ if (lastDraggedIndex < minReached) {
+ minReached = lastDraggedIndex
+ }
+
+ dragListener?.selectRange(initialSelection, lastDraggedIndex, minReached, maxReached)
+
+ if (initialSelection == lastDraggedIndex) {
+ minReached = lastDraggedIndex
+ maxReached = lastDraggedIndex
+ }
+ }
+
+ return true
+ }
+ }
+ }
+
+ return if (isZoomEnabled) {
+ scaleDetector.onTouchEvent(ev)
+ } else {
+ true
+ }
+ }
+
+ fun setupDragListener(dragListener: MyDragListener?) {
+ isDragSelectionEnabled = dragListener != null
+ this.dragListener = dragListener
+ }
+
+ fun setupZoomListener(zoomListener: MyZoomListener?) {
+ isZoomEnabled = zoomListener != null
+ this.zoomListener = zoomListener
+ }
+
+ fun setDragSelectActive(initialSelection: Int) {
+ if (dragSelectActive || !isDragSelectionEnabled)
+ return
+
+ lastDraggedIndex = -1
+ minReached = -1
+ maxReached = -1
+ this.initialSelection = initialSelection
+ dragSelectActive = true
+ dragListener?.selectItem(initialSelection)
+ }
+
+ private fun getItemPosition(e: MotionEvent): Int {
+ val v = findChildViewUnder(e.x, e.y) ?: return RecyclerView.NO_POSITION
+
+ if (v.tag == null || v.tag !is RecyclerView.ViewHolder) {
+ throw IllegalStateException("Make sure your adapter makes a call to super.onBindViewHolder(), and doesn't override itemView tags.")
+ }
+
+ val holder = v.tag as RecyclerView.ViewHolder
+ return holder.adapterPosition
+ }
+
+ override fun onScrollStateChanged(state: Int) {
+ super.onScrollStateChanged(state)
+ if (endlessScrollListener != null) {
+ if (totalItemCount == 0) {
+ totalItemCount = adapter.itemCount
+ }
+
+ if (state == SCROLL_STATE_IDLE) {
+ val lastVisiblePosition = linearLayoutManager?.findLastVisibleItemPosition() ?: 0
+ if (lastVisiblePosition != lastMaxItemIndex && lastVisiblePosition == totalItemCount - 1) {
+ lastMaxItemIndex = lastVisiblePosition
+ endlessScrollListener!!.updateBottom()
+ }
+
+ val firstVisiblePosition = linearLayoutManager?.findFirstVisibleItemPosition() ?: -1
+ if (firstVisiblePosition == 0) {
+ endlessScrollListener!!.updateTop()
+ }
+ }
+ }
+ }
+
+ override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) {
+ super.onScrollChanged(l, t, oldl, oldt)
+ if (recyclerScrollCallback != null) {
+ if (childCount > 0) {
+ val firstVisiblePosition = getChildAdapterPosition(getChildAt(0))
+ val firstVisibleChild = getChildAt(0)
+ if (firstVisibleChild != null) {
+ if (mPrevFirstVisiblePosition < firstVisiblePosition) {
+ mPrevScrolledChildrenHeight += mPrevFirstVisibleChildHeight
+ }
+
+ if (firstVisiblePosition == 0) {
+ mPrevFirstVisibleChildHeight = firstVisibleChild.height
+ mPrevScrolledChildrenHeight = 0
+ }
+
+ if (mPrevFirstVisibleChildHeight < 0) {
+ mPrevFirstVisibleChildHeight = 0
+ }
+
+ mScrollY = mPrevScrolledChildrenHeight - firstVisibleChild.top
+ recyclerScrollCallback?.onScrolled(mScrollY)
+ }
+ }
+ }
+ }
+
+ class GestureListener(val gestureListener: MyGestureListener) : ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ private val ZOOM_IN_THRESHOLD = -0.4f
+ private val ZOOM_OUT_THRESHOLD = 0.15f
+
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ gestureListener.apply {
+ if (System.currentTimeMillis() - getLastUp() < 1000)
+ return false
+
+ val diff = getScaleFactor() - detector.scaleFactor
+ if (diff < ZOOM_IN_THRESHOLD && getScaleFactor() == 1.0f) {
+ getZoomListener()?.zoomIn()
+ setScaleFactor(detector.scaleFactor)
+ } else if (diff > ZOOM_OUT_THRESHOLD && getScaleFactor() == 1.0f) {
+ getZoomListener()?.zoomOut()
+ setScaleFactor(detector.scaleFactor)
+ }
+ }
+ return false
+ }
+ }
+
+ interface MyZoomListener {
+ fun zoomOut()
+
+ fun zoomIn()
+ }
+
+ interface MyDragListener {
+ fun selectItem(position: Int)
+
+ fun selectRange(initialSelection: Int, lastDraggedIndex: Int, minReached: Int, maxReached: Int)
+ }
+
+ interface MyGestureListener {
+ fun getLastUp(): Long
+
+ fun getScaleFactor(): Float
+
+ fun setScaleFactor(value: Float)
+
+ fun getZoomListener(): MyZoomListener?
+ }
+
+ interface EndlessScrollListener {
+ fun updateTop()
+
+ fun updateBottom()
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyScrollView.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyScrollView.kt
new file mode 100644
index 0000000..c36a6c6
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyScrollView.kt
@@ -0,0 +1,37 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.ScrollView
+
+class MyScrollView : ScrollView {
+ var isScrollable = true
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ return when (ev.action) {
+ MotionEvent.ACTION_DOWN -> {
+ if (isScrollable) {
+ super.onTouchEvent(ev)
+ } else {
+ isScrollable
+ }
+ }
+ else -> super.onTouchEvent(ev)
+ }
+ }
+
+ override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
+ return if (!isScrollable) {
+ false
+ } else {
+ super.onInterceptTouchEvent(ev)
+ }
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MySeekBar.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MySeekBar.kt
new file mode 100644
index 0000000..07b41c7
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MySeekBar.kt
@@ -0,0 +1,20 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.util.AttributeSet
+import android.widget.SeekBar
+
+class MySeekBar : SeekBar {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) {
+ progressDrawable.colorFilter = PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN)
+ thumb.colorFilter = PorterDuffColorFilter(accentColor, PorterDuff.Mode.SRC_IN)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MySwitchCompat.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MySwitchCompat.kt
new file mode 100644
index 0000000..81a7168
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MySwitchCompat.kt
@@ -0,0 +1,26 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.support.v4.graphics.drawable.DrawableCompat
+import android.support.v7.widget.SwitchCompat
+import android.util.AttributeSet
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.adjustAlpha
+
+class MySwitchCompat : SwitchCompat {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) {
+ setTextColor(textColor)
+ val states = arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked))
+ val thumbColors = intArrayOf(resources.getColor(R.color.thumb_deactivated), accentColor)
+ val trackColors = intArrayOf(resources.getColor(R.color.track_deactivated), accentColor.adjustAlpha(0.3f))
+ DrawableCompat.setTintList(DrawableCompat.wrap(thumbDrawable), ColorStateList(states, thumbColors))
+ DrawableCompat.setTintList(DrawableCompat.wrap(trackDrawable), ColorStateList(states, trackColors))
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyTextView.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyTextView.kt
new file mode 100644
index 0000000..47fa43a
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyTextView.kt
@@ -0,0 +1,18 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.TextView
+
+class MyTextView : TextView {
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle)
+
+ fun setColors(textColor: Int, accentColor: Int, backgroundColor: Int) {
+ setTextColor(textColor)
+ setLinkTextColor(accentColor)
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyViewPager.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyViewPager.kt
new file mode 100644
index 0000000..bb2f299
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/MyViewPager.kt
@@ -0,0 +1,32 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+
+import com.booking.rtlviewpager.RtlViewPager
+
+class MyViewPager : RtlViewPager {
+
+ constructor(context: Context) : super(context)
+
+ constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
+
+ override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
+ try {
+ return super.onInterceptTouchEvent(ev)
+ } catch (ignored: Exception) {
+ }
+
+ return false
+ }
+
+ override fun onTouchEvent(ev: MotionEvent): Boolean {
+ try {
+ return super.onTouchEvent(ev)
+ } catch (ignored: Exception) {
+ }
+
+ return false
+ }
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/PatternTab.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/PatternTab.kt
new file mode 100644
index 0000000..15f8866
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/PatternTab.kt
@@ -0,0 +1,89 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.os.Handler
+import android.util.AttributeSet
+import android.view.MotionEvent
+import android.widget.RelativeLayout
+import com.andrognito.patternlockview.PatternLockView
+import com.andrognito.patternlockview.listener.PatternLockViewListener
+import com.andrognito.patternlockview.utils.PatternLockUtils
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.baseConfig
+import com.simplemobiletools.commons.extensions.toast
+import com.simplemobiletools.commons.extensions.updateTextColors
+import com.simplemobiletools.commons.helpers.PROTECTION_PATTERN
+import com.simplemobiletools.commons.interfaces.HashListener
+import com.simplemobiletools.commons.interfaces.SecurityTab
+import kotlinx.android.synthetic.main.tab_pattern.view.*
+
+class PatternTab(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs), SecurityTab {
+ private var hash = ""
+ private var requiredHash = ""
+ private var scrollView: MyScrollView? = null
+ lateinit var hashListener: HashListener
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ val textColor = context.baseConfig.textColor
+ context.updateTextColors(pattern_lock_holder)
+
+ pattern_lock_view.setOnTouchListener { v, event ->
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> scrollView?.isScrollable = false
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> scrollView?.isScrollable = true
+ }
+ false
+ }
+
+ pattern_lock_view.correctStateColor = context.baseConfig.primaryColor
+ pattern_lock_view.normalStateColor = textColor
+ pattern_lock_view.addPatternLockListener(object : PatternLockViewListener {
+ override fun onComplete(pattern: MutableList?) {
+ receivedHash(PatternLockUtils.patternToSha1(pattern_lock_view, pattern))
+ }
+
+ override fun onCleared() {}
+
+ override fun onStarted() {}
+
+ override fun onProgress(progressPattern: MutableList?) {}
+ })
+ }
+
+ override fun initTab(requiredHash: String, listener: HashListener, scrollView: MyScrollView) {
+ this.requiredHash = requiredHash
+ this.scrollView = scrollView
+ hash = requiredHash
+ hashListener = listener
+ }
+
+ private fun receivedHash(newHash: String) {
+ when {
+ hash.isEmpty() -> {
+ hash = newHash
+ pattern_lock_view.clearPattern()
+ pattern_lock_title.setText(R.string.repeat_pattern)
+ }
+ hash == newHash -> {
+ pattern_lock_view.setViewMode(PatternLockView.PatternViewMode.CORRECT)
+ Handler().postDelayed({
+ hashListener.receivedHash(hash, PROTECTION_PATTERN)
+ }, 300)
+ }
+ else -> {
+ pattern_lock_view.setViewMode(PatternLockView.PatternViewMode.WRONG)
+ context.toast(R.string.wrong_pattern)
+ Handler().postDelayed({
+ pattern_lock_view.clearPattern()
+ if (requiredHash.isEmpty()) {
+ hash = ""
+ pattern_lock_title.setText(R.string.insert_pattern)
+ }
+ }, 1000)
+ }
+ }
+ }
+
+ override fun visibilityChanged(isVisible: Boolean) {}
+}
diff --git a/commons/src/main/kotlin/com/simplemobiletools/commons/views/PinTab.kt b/commons/src/main/kotlin/com/simplemobiletools/commons/views/PinTab.kt
new file mode 100644
index 0000000..35b3979
--- /dev/null
+++ b/commons/src/main/kotlin/com/simplemobiletools/commons/views/PinTab.kt
@@ -0,0 +1,105 @@
+package com.simplemobiletools.commons.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.RelativeLayout
+import com.simplemobiletools.commons.R
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.PROTECTION_PIN
+import com.simplemobiletools.commons.interfaces.HashListener
+import com.simplemobiletools.commons.interfaces.SecurityTab
+import kotlinx.android.synthetic.main.tab_pin.view.*
+import java.math.BigInteger
+import java.security.MessageDigest
+import java.util.*
+
+class PinTab(context: Context, attrs: AttributeSet) : RelativeLayout(context, attrs), SecurityTab {
+ private var hash = ""
+ private var requiredHash = ""
+ private var pin = ""
+ lateinit var hashListener: HashListener
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ context.updateTextColors(pin_lock_holder)
+
+ pin_0.setOnClickListener { addNumber("0") }
+ pin_1.setOnClickListener { addNumber("1") }
+ pin_2.setOnClickListener { addNumber("2") }
+ pin_3.setOnClickListener { addNumber("3") }
+ pin_4.setOnClickListener { addNumber("4") }
+ pin_5.setOnClickListener { addNumber("5") }
+ pin_6.setOnClickListener { addNumber("6") }
+ pin_7.setOnClickListener { addNumber("7") }
+ pin_8.setOnClickListener { addNumber("8") }
+ pin_9.setOnClickListener { addNumber("9") }
+ pin_c.setOnClickListener { clear() }
+ pin_ok.setOnClickListener { confirmPIN() }
+ pin_ok.applyColorFilter(context.baseConfig.textColor)
+ }
+
+ override fun initTab(requiredHash: String, listener: HashListener, scrollView: MyScrollView) {
+ this.requiredHash = requiredHash
+ hash = requiredHash
+ hashListener = listener
+ }
+
+ private fun addNumber(number: String) {
+ if (pin.length < 10) {
+ pin += number
+ updatePinCode()
+ }
+ performHapticFeedback()
+ }
+
+ private fun clear() {
+ if (pin.isNotEmpty()) {
+ pin = pin.substring(0, pin.length - 1)
+ updatePinCode()
+ }
+ performHapticFeedback()
+ }
+
+ private fun confirmPIN() {
+ val newHash = getHashedPin()
+ if (pin.isEmpty()) {
+ context.toast(R.string.please_enter_pin)
+ } else if (hash.isEmpty()) {
+ hash = newHash
+ resetPin()
+ pin_lock_title.setText(R.string.repeat_pin)
+ } else if (hash == newHash) {
+ hashListener.receivedHash(hash, PROTECTION_PIN)
+ } else {
+ resetPin()
+ context.toast(R.string.wrong_pin)
+ if (requiredHash.isEmpty()) {
+ hash = ""
+ pin_lock_title.setText(R.string.enter_pin)
+ }
+ }
+ performHapticFeedback()
+ }
+
+ private fun resetPin() {
+ pin = ""
+ pin_lock_current_pin.text = ""
+ }
+
+ private fun updatePinCode() {
+ pin_lock_current_pin.text = "*".repeat(pin.length)
+ if (hash.isNotEmpty() && hash == getHashedPin()) {
+ hashListener.receivedHash(hash, PROTECTION_PIN)
+ }
+ }
+
+ private fun getHashedPin(): String {
+ val messageDigest = MessageDigest.getInstance("SHA-1")
+ messageDigest.update(pin.toByteArray(charset("UTF-8")))
+ val digest = messageDigest.digest()
+ val bigInteger = BigInteger(1, digest)
+ return String.format(Locale.getDefault(), "%0${digest.size * 2}x", bigInteger).toLowerCase()
+ }
+
+ override fun visibilityChanged(isVisible: Boolean) {}
+}
diff --git a/commons/src/main/res/drawable-hdpi/ic_arrow_right.png b/commons/src/main/res/drawable-hdpi/ic_arrow_right.png
new file mode 100644
index 0000000..b8c16a3
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_arrow_right.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_attach_file.png b/commons/src/main/res/drawable-hdpi/ic_attach_file.png
new file mode 100644
index 0000000..29dfa5d
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_attach_file.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_bell.png b/commons/src/main/res/drawable-hdpi/ic_bell.png
new file mode 100644
index 0000000..7e2f86f
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_bell.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_camera.png b/commons/src/main/res/drawable-hdpi/ic_camera.png
new file mode 100644
index 0000000..497c88c
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_camera.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_check.png b/commons/src/main/res/drawable-hdpi/ic_check.png
new file mode 100644
index 0000000..729f290
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_check.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_chevron_right.png b/commons/src/main/res/drawable-hdpi/ic_chevron_right.png
new file mode 100644
index 0000000..ea11e3f
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_chevron_right.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_circle_filled.png b/commons/src/main/res/drawable-hdpi/ic_circle_filled.png
new file mode 100644
index 0000000..cdc17db
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_circle_filled.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_clock.png b/commons/src/main/res/drawable-hdpi/ic_clock.png
new file mode 100644
index 0000000..4651478
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_clock.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_copy.png b/commons/src/main/res/drawable-hdpi/ic_copy.png
new file mode 100644
index 0000000..70eb073
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_copy.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_cross.png b/commons/src/main/res/drawable-hdpi/ic_cross.png
new file mode 100644
index 0000000..943ce2e
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_cross.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_delete.png b/commons/src/main/res/drawable-hdpi/ic_delete.png
new file mode 100644
index 0000000..4a9f769
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_delete.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_edit.png b/commons/src/main/res/drawable-hdpi/ic_edit.png
new file mode 100644
index 0000000..d3ff58d
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_edit.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_email.png b/commons/src/main/res/drawable-hdpi/ic_email.png
new file mode 100644
index 0000000..852774d
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_email.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_email_big.png b/commons/src/main/res/drawable-hdpi/ic_email_big.png
new file mode 100644
index 0000000..8deac81
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_email_big.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_facebook.png b/commons/src/main/res/drawable-hdpi/ic_facebook.png
new file mode 100644
index 0000000..9992cbc
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_facebook.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_file.png b/commons/src/main/res/drawable-hdpi/ic_file.png
new file mode 100644
index 0000000..84755e4
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_file.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_filter.png b/commons/src/main/res/drawable-hdpi/ic_filter.png
new file mode 100644
index 0000000..7e8a6b5
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_filter.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_fingerprint.png b/commons/src/main/res/drawable-hdpi/ic_fingerprint.png
new file mode 100644
index 0000000..66b6cf3
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_fingerprint.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_folder.png b/commons/src/main/res/drawable-hdpi/ic_folder.png
new file mode 100644
index 0000000..02ea533
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_folder.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_gplus.png b/commons/src/main/res/drawable-hdpi/ic_gplus.png
new file mode 100644
index 0000000..897f103
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_gplus.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_hide.png b/commons/src/main/res/drawable-hdpi/ic_hide.png
new file mode 100644
index 0000000..b46cb21
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_hide.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_info.png b/commons/src/main/res/drawable-hdpi/ic_info.png
new file mode 100644
index 0000000..d2c435f
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_info.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_label.png b/commons/src/main/res/drawable-hdpi/ic_label.png
new file mode 100644
index 0000000..5d336d6
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_label.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_minus.png b/commons/src/main/res/drawable-hdpi/ic_minus.png
new file mode 100644
index 0000000..ee654f9
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_minus.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_minus_circle.png b/commons/src/main/res/drawable-hdpi/ic_minus_circle.png
new file mode 100644
index 0000000..021e6f0
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_minus_circle.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_open.png b/commons/src/main/res/drawable-hdpi/ic_open.png
new file mode 100644
index 0000000..f25d504
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_open.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_orientation_auto.png b/commons/src/main/res/drawable-hdpi/ic_orientation_auto.png
new file mode 100644
index 0000000..ca8874b
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_orientation_auto.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_orientation_landscape.png b/commons/src/main/res/drawable-hdpi/ic_orientation_landscape.png
new file mode 100644
index 0000000..d9779d4
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_orientation_landscape.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_orientation_portrait.png b/commons/src/main/res/drawable-hdpi/ic_orientation_portrait.png
new file mode 100644
index 0000000..1383d3c
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_orientation_portrait.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_pause.png b/commons/src/main/res/drawable-hdpi/ic_pause.png
new file mode 100644
index 0000000..dfca9cd
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_pause.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_person.png b/commons/src/main/res/drawable-hdpi/ic_person.png
new file mode 100644
index 0000000..a48e982
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_person.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_phone.png b/commons/src/main/res/drawable-hdpi/ic_phone.png
new file mode 100644
index 0000000..6f4dcea
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_phone.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_place.png b/commons/src/main/res/drawable-hdpi/ic_place.png
new file mode 100644
index 0000000..976811e
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_place.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_place_big.png b/commons/src/main/res/drawable-hdpi/ic_place_big.png
new file mode 100644
index 0000000..b345cff
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_place_big.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_play.png b/commons/src/main/res/drawable-hdpi/ic_play.png
new file mode 100644
index 0000000..d561e77
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_play.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_plus.png b/commons/src/main/res/drawable-hdpi/ic_plus.png
new file mode 100644
index 0000000..76f27e6
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_plus.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_properties.png b/commons/src/main/res/drawable-hdpi/ic_properties.png
new file mode 100644
index 0000000..d2c435f
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_properties.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_redo.png b/commons/src/main/res/drawable-hdpi/ic_redo.png
new file mode 100644
index 0000000..b84447c
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_redo.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_redo_big.png b/commons/src/main/res/drawable-hdpi/ic_redo_big.png
new file mode 100644
index 0000000..274dc1f
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_redo_big.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_rename_new.png b/commons/src/main/res/drawable-hdpi/ic_rename_new.png
new file mode 100644
index 0000000..02d7ddf
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_rename_new.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_repeat.png b/commons/src/main/res/drawable-hdpi/ic_repeat.png
new file mode 100644
index 0000000..dc81e85
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_repeat.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_reset.png b/commons/src/main/res/drawable-hdpi/ic_reset.png
new file mode 100644
index 0000000..2777c43
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_reset.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_save.png b/commons/src/main/res/drawable-hdpi/ic_save.png
new file mode 100644
index 0000000..dd3f106
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_save.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_sd_card.png b/commons/src/main/res/drawable-hdpi/ic_sd_card.png
new file mode 100644
index 0000000..60397d4
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_sd_card.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_search.png b/commons/src/main/res/drawable-hdpi/ic_search.png
new file mode 100644
index 0000000..bbfbc96
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_search.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_select_all.png b/commons/src/main/res/drawable-hdpi/ic_select_all.png
new file mode 100644
index 0000000..2d971a9
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_select_all.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_share.png b/commons/src/main/res/drawable-hdpi/ic_share.png
new file mode 100644
index 0000000..b09a692
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_share.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_sms.png b/commons/src/main/res/drawable-hdpi/ic_sms.png
new file mode 100644
index 0000000..12a1ef8
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_sms.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_sms_big.png b/commons/src/main/res/drawable-hdpi/ic_sms_big.png
new file mode 100644
index 0000000..dc52211
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_sms_big.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_snooze.png b/commons/src/main/res/drawable-hdpi/ic_snooze.png
new file mode 100644
index 0000000..43e1707
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_snooze.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_snooze_big.png b/commons/src/main/res/drawable-hdpi/ic_snooze_big.png
new file mode 100644
index 0000000..ef799c6
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_snooze_big.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_sort.png b/commons/src/main/res/drawable-hdpi/ic_sort.png
new file mode 100644
index 0000000..c62475e
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_sort.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_star_off.png b/commons/src/main/res/drawable-hdpi/ic_star_off.png
new file mode 100644
index 0000000..e302ef6
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_star_off.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_star_off_big.png b/commons/src/main/res/drawable-hdpi/ic_star_off_big.png
new file mode 100644
index 0000000..c9ab328
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_star_off_big.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_star_on.png b/commons/src/main/res/drawable-hdpi/ic_star_on.png
new file mode 100644
index 0000000..86eecdd
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_star_on.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_star_on_big.png b/commons/src/main/res/drawable-hdpi/ic_star_on_big.png
new file mode 100644
index 0000000..111b191
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_star_on_big.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_stop.png b/commons/src/main/res/drawable-hdpi/ic_stop.png
new file mode 100644
index 0000000..83e7971
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_stop.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_undo.png b/commons/src/main/res/drawable-hdpi/ic_undo.png
new file mode 100644
index 0000000..326ef46
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_undo.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_undo_big.png b/commons/src/main/res/drawable-hdpi/ic_undo_big.png
new file mode 100644
index 0000000..1e1e3d8
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_undo_big.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_unhide.png b/commons/src/main/res/drawable-hdpi/ic_unhide.png
new file mode 100644
index 0000000..4791d9f
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_unhide.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_usb.png b/commons/src/main/res/drawable-hdpi/ic_usb.png
new file mode 100644
index 0000000..d3b95c0
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_usb.png differ
diff --git a/commons/src/main/res/drawable-hdpi/ic_vibrate.png b/commons/src/main/res/drawable-hdpi/ic_vibrate.png
new file mode 100644
index 0000000..f258534
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/ic_vibrate.png differ
diff --git a/commons/src/main/res/drawable-hdpi/img_write_storage.png b/commons/src/main/res/drawable-hdpi/img_write_storage.png
new file mode 100644
index 0000000..b00028a
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/img_write_storage.png differ
diff --git a/commons/src/main/res/drawable-hdpi/img_write_storage_otg.png b/commons/src/main/res/drawable-hdpi/img_write_storage_otg.png
new file mode 100644
index 0000000..a115378
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/img_write_storage_otg.png differ
diff --git a/commons/src/main/res/drawable-hdpi/img_write_storage_sd.png b/commons/src/main/res/drawable-hdpi/img_write_storage_sd.png
new file mode 100644
index 0000000..458c555
Binary files /dev/null and b/commons/src/main/res/drawable-hdpi/img_write_storage_sd.png differ
diff --git a/commons/src/main/res/drawable-ldrtl/fastscroller_handle_vertical.xml b/commons/src/main/res/drawable-ldrtl/fastscroller_handle_vertical.xml
new file mode 100644
index 0000000..2e68d6a
--- /dev/null
+++ b/commons/src/main/res/drawable-ldrtl/fastscroller_handle_vertical.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/drawable-nodpi/img_color_picker_hue.png b/commons/src/main/res/drawable-nodpi/img_color_picker_hue.png
new file mode 100644
index 0000000..09d2839
Binary files /dev/null and b/commons/src/main/res/drawable-nodpi/img_color_picker_hue.png differ
diff --git a/commons/src/main/res/drawable-v21/selector.xml b/commons/src/main/res/drawable-v21/selector.xml
new file mode 100644
index 0000000..a24cfe2
--- /dev/null
+++ b/commons/src/main/res/drawable-v21/selector.xml
@@ -0,0 +1,17 @@
+
+
+ -
+
+
+
+
+ -
+
+ -
+
+
+
+
+
diff --git a/commons/src/main/res/drawable-xhdpi/ic_arrow_right.png b/commons/src/main/res/drawable-xhdpi/ic_arrow_right.png
new file mode 100644
index 0000000..878b6e5
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_arrow_right.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_attach_file.png b/commons/src/main/res/drawable-xhdpi/ic_attach_file.png
new file mode 100644
index 0000000..c3ff2bd
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_attach_file.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_bell.png b/commons/src/main/res/drawable-xhdpi/ic_bell.png
new file mode 100644
index 0000000..66df522
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_bell.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_camera.png b/commons/src/main/res/drawable-xhdpi/ic_camera.png
new file mode 100644
index 0000000..be9fb22
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_camera.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_check.png b/commons/src/main/res/drawable-xhdpi/ic_check.png
new file mode 100644
index 0000000..3b2b65d
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_check.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_chevron_right.png b/commons/src/main/res/drawable-xhdpi/ic_chevron_right.png
new file mode 100644
index 0000000..ff7ed2f
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_chevron_right.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_circle_filled.png b/commons/src/main/res/drawable-xhdpi/ic_circle_filled.png
new file mode 100644
index 0000000..7cf5ffb
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_circle_filled.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_clock.png b/commons/src/main/res/drawable-xhdpi/ic_clock.png
new file mode 100644
index 0000000..52cf597
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_clock.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_copy.png b/commons/src/main/res/drawable-xhdpi/ic_copy.png
new file mode 100644
index 0000000..537fd4e
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_copy.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_cross.png b/commons/src/main/res/drawable-xhdpi/ic_cross.png
new file mode 100644
index 0000000..150058a
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_cross.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_delete.png b/commons/src/main/res/drawable-xhdpi/ic_delete.png
new file mode 100644
index 0000000..388b5b0
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_delete.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_edit.png b/commons/src/main/res/drawable-xhdpi/ic_edit.png
new file mode 100644
index 0000000..5a06bff
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_edit.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_email.png b/commons/src/main/res/drawable-xhdpi/ic_email.png
new file mode 100644
index 0000000..9756b80
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_email.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_email_big.png b/commons/src/main/res/drawable-xhdpi/ic_email_big.png
new file mode 100644
index 0000000..fa89f47
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_email_big.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_facebook.png b/commons/src/main/res/drawable-xhdpi/ic_facebook.png
new file mode 100644
index 0000000..ba7b79e
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_facebook.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_file.png b/commons/src/main/res/drawable-xhdpi/ic_file.png
new file mode 100644
index 0000000..798ebd4
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_file.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_filter.png b/commons/src/main/res/drawable-xhdpi/ic_filter.png
new file mode 100644
index 0000000..9416c70
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_filter.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_fingerprint.png b/commons/src/main/res/drawable-xhdpi/ic_fingerprint.png
new file mode 100644
index 0000000..8793f6a
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_fingerprint.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_folder.png b/commons/src/main/res/drawable-xhdpi/ic_folder.png
new file mode 100644
index 0000000..71a5a13
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_folder.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_gplus.png b/commons/src/main/res/drawable-xhdpi/ic_gplus.png
new file mode 100644
index 0000000..47bd6ef
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_gplus.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_hide.png b/commons/src/main/res/drawable-xhdpi/ic_hide.png
new file mode 100644
index 0000000..62db275
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_hide.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_info.png b/commons/src/main/res/drawable-xhdpi/ic_info.png
new file mode 100644
index 0000000..4fd6a43
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_info.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_label.png b/commons/src/main/res/drawable-xhdpi/ic_label.png
new file mode 100644
index 0000000..10b6b6a
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_label.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_minus.png b/commons/src/main/res/drawable-xhdpi/ic_minus.png
new file mode 100644
index 0000000..e1f9bde
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_minus.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_minus_circle.png b/commons/src/main/res/drawable-xhdpi/ic_minus_circle.png
new file mode 100644
index 0000000..3ad570e
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_minus_circle.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_open.png b/commons/src/main/res/drawable-xhdpi/ic_open.png
new file mode 100644
index 0000000..1ca52cd
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_open.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_orientation_auto.png b/commons/src/main/res/drawable-xhdpi/ic_orientation_auto.png
new file mode 100644
index 0000000..fc7b125
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_orientation_auto.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_orientation_landscape.png b/commons/src/main/res/drawable-xhdpi/ic_orientation_landscape.png
new file mode 100644
index 0000000..a9a0c65
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_orientation_landscape.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_orientation_portrait.png b/commons/src/main/res/drawable-xhdpi/ic_orientation_portrait.png
new file mode 100644
index 0000000..b748114
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_orientation_portrait.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_pause.png b/commons/src/main/res/drawable-xhdpi/ic_pause.png
new file mode 100644
index 0000000..15e24fa
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_pause.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_person.png b/commons/src/main/res/drawable-xhdpi/ic_person.png
new file mode 100644
index 0000000..184f741
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_person.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_phone.png b/commons/src/main/res/drawable-xhdpi/ic_phone.png
new file mode 100644
index 0000000..90ead2e
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_phone.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_place.png b/commons/src/main/res/drawable-xhdpi/ic_place.png
new file mode 100644
index 0000000..d226e78
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_place.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_place_big.png b/commons/src/main/res/drawable-xhdpi/ic_place_big.png
new file mode 100644
index 0000000..078b10d
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_place_big.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_play.png b/commons/src/main/res/drawable-xhdpi/ic_play.png
new file mode 100644
index 0000000..ae32825
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_play.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_plus.png b/commons/src/main/res/drawable-xhdpi/ic_plus.png
new file mode 100644
index 0000000..d8ef0f4
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_plus.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_properties.png b/commons/src/main/res/drawable-xhdpi/ic_properties.png
new file mode 100644
index 0000000..4fd6a43
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_properties.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_redo.png b/commons/src/main/res/drawable-xhdpi/ic_redo.png
new file mode 100644
index 0000000..9840912
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_redo.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_redo_big.png b/commons/src/main/res/drawable-xhdpi/ic_redo_big.png
new file mode 100644
index 0000000..ed7464d
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_redo_big.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_rename_new.png b/commons/src/main/res/drawable-xhdpi/ic_rename_new.png
new file mode 100644
index 0000000..238d84e
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_rename_new.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_repeat.png b/commons/src/main/res/drawable-xhdpi/ic_repeat.png
new file mode 100644
index 0000000..61d33ff
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_repeat.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_reset.png b/commons/src/main/res/drawable-xhdpi/ic_reset.png
new file mode 100644
index 0000000..17e79ef
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_reset.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_save.png b/commons/src/main/res/drawable-xhdpi/ic_save.png
new file mode 100644
index 0000000..adda095
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_save.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_sd_card.png b/commons/src/main/res/drawable-xhdpi/ic_sd_card.png
new file mode 100644
index 0000000..fbcde51
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_sd_card.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_search.png b/commons/src/main/res/drawable-xhdpi/ic_search.png
new file mode 100644
index 0000000..bfc3e39
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_search.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_select_all.png b/commons/src/main/res/drawable-xhdpi/ic_select_all.png
new file mode 100644
index 0000000..b99012a
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_select_all.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_share.png b/commons/src/main/res/drawable-xhdpi/ic_share.png
new file mode 100644
index 0000000..22a8783
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_share.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_sms.png b/commons/src/main/res/drawable-xhdpi/ic_sms.png
new file mode 100644
index 0000000..9818088
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_sms.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_sms_big.png b/commons/src/main/res/drawable-xhdpi/ic_sms_big.png
new file mode 100644
index 0000000..a3d45a5
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_sms_big.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_snooze.png b/commons/src/main/res/drawable-xhdpi/ic_snooze.png
new file mode 100644
index 0000000..e27af11
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_snooze.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_snooze_big.png b/commons/src/main/res/drawable-xhdpi/ic_snooze_big.png
new file mode 100644
index 0000000..1761d04
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_snooze_big.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_sort.png b/commons/src/main/res/drawable-xhdpi/ic_sort.png
new file mode 100644
index 0000000..1f9dccf
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_sort.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_star_off.png b/commons/src/main/res/drawable-xhdpi/ic_star_off.png
new file mode 100644
index 0000000..c7a5388
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_star_off.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_star_off_big.png b/commons/src/main/res/drawable-xhdpi/ic_star_off_big.png
new file mode 100644
index 0000000..7e41906
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_star_off_big.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_star_on.png b/commons/src/main/res/drawable-xhdpi/ic_star_on.png
new file mode 100644
index 0000000..9143406
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_star_on.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_star_on_big.png b/commons/src/main/res/drawable-xhdpi/ic_star_on_big.png
new file mode 100644
index 0000000..aa58792
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_star_on_big.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_stop.png b/commons/src/main/res/drawable-xhdpi/ic_stop.png
new file mode 100644
index 0000000..7749a7a
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_stop.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_undo.png b/commons/src/main/res/drawable-xhdpi/ic_undo.png
new file mode 100644
index 0000000..035b9f2
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_undo.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_undo_big.png b/commons/src/main/res/drawable-xhdpi/ic_undo_big.png
new file mode 100644
index 0000000..6b10718
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_undo_big.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_unhide.png b/commons/src/main/res/drawable-xhdpi/ic_unhide.png
new file mode 100644
index 0000000..604bfd0
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_unhide.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_usb.png b/commons/src/main/res/drawable-xhdpi/ic_usb.png
new file mode 100644
index 0000000..3a3a56f
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_usb.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/ic_vibrate.png b/commons/src/main/res/drawable-xhdpi/ic_vibrate.png
new file mode 100644
index 0000000..06755aa
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/ic_vibrate.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/img_write_storage.png b/commons/src/main/res/drawable-xhdpi/img_write_storage.png
new file mode 100644
index 0000000..c732308
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/img_write_storage.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/img_write_storage_otg.png b/commons/src/main/res/drawable-xhdpi/img_write_storage_otg.png
new file mode 100644
index 0000000..8ae538a
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/img_write_storage_otg.png differ
diff --git a/commons/src/main/res/drawable-xhdpi/img_write_storage_sd.png b/commons/src/main/res/drawable-xhdpi/img_write_storage_sd.png
new file mode 100644
index 0000000..e5c34f6
Binary files /dev/null and b/commons/src/main/res/drawable-xhdpi/img_write_storage_sd.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_arrow_right.png b/commons/src/main/res/drawable-xxhdpi/ic_arrow_right.png
new file mode 100644
index 0000000..8c4c394
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_arrow_right.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_attach_file.png b/commons/src/main/res/drawable-xxhdpi/ic_attach_file.png
new file mode 100644
index 0000000..7091eca
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_attach_file.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_bell.png b/commons/src/main/res/drawable-xxhdpi/ic_bell.png
new file mode 100644
index 0000000..75fb548
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_bell.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_camera.png b/commons/src/main/res/drawable-xxhdpi/ic_camera.png
new file mode 100644
index 0000000..c8e69dc
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_camera.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_check.png b/commons/src/main/res/drawable-xxhdpi/ic_check.png
new file mode 100644
index 0000000..2c2ad77
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_check.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_chevron_right.png b/commons/src/main/res/drawable-xxhdpi/ic_chevron_right.png
new file mode 100644
index 0000000..13fb795
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_chevron_right.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_circle_filled.png b/commons/src/main/res/drawable-xxhdpi/ic_circle_filled.png
new file mode 100644
index 0000000..1fdd4fc
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_circle_filled.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_clock.png b/commons/src/main/res/drawable-xxhdpi/ic_clock.png
new file mode 100644
index 0000000..cf9551f
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_clock.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_copy.png b/commons/src/main/res/drawable-xxhdpi/ic_copy.png
new file mode 100644
index 0000000..9dff893
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_copy.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_cross.png b/commons/src/main/res/drawable-xxhdpi/ic_cross.png
new file mode 100644
index 0000000..fcfcd26
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_cross.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_delete.png b/commons/src/main/res/drawable-xxhdpi/ic_delete.png
new file mode 100644
index 0000000..3fcdfdb
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_delete.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_edit.png b/commons/src/main/res/drawable-xxhdpi/ic_edit.png
new file mode 100644
index 0000000..02e19d0
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_edit.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_email.png b/commons/src/main/res/drawable-xxhdpi/ic_email.png
new file mode 100644
index 0000000..fa89f47
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_email.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_email_big.png b/commons/src/main/res/drawable-xxhdpi/ic_email_big.png
new file mode 100644
index 0000000..6d6c0f1
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_email_big.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_facebook.png b/commons/src/main/res/drawable-xxhdpi/ic_facebook.png
new file mode 100644
index 0000000..53a3f01
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_facebook.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_file.png b/commons/src/main/res/drawable-xxhdpi/ic_file.png
new file mode 100644
index 0000000..f3e153b
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_file.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_filter.png b/commons/src/main/res/drawable-xxhdpi/ic_filter.png
new file mode 100644
index 0000000..1263ae8
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_filter.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_fingerprint.png b/commons/src/main/res/drawable-xxhdpi/ic_fingerprint.png
new file mode 100644
index 0000000..b7e20cd
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_fingerprint.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_folder.png b/commons/src/main/res/drawable-xxhdpi/ic_folder.png
new file mode 100644
index 0000000..b93d5a1
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_folder.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_gplus.png b/commons/src/main/res/drawable-xxhdpi/ic_gplus.png
new file mode 100644
index 0000000..d580cd5
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_gplus.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_hide.png b/commons/src/main/res/drawable-xxhdpi/ic_hide.png
new file mode 100644
index 0000000..9ab0c37
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_hide.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_info.png b/commons/src/main/res/drawable-xxhdpi/ic_info.png
new file mode 100644
index 0000000..db51fc7
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_info.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_label.png b/commons/src/main/res/drawable-xxhdpi/ic_label.png
new file mode 100644
index 0000000..bd87241
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_label.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_minus.png b/commons/src/main/res/drawable-xxhdpi/ic_minus.png
new file mode 100644
index 0000000..c4808d4
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_minus.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_minus_circle.png b/commons/src/main/res/drawable-xxhdpi/ic_minus_circle.png
new file mode 100644
index 0000000..67165c8
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_minus_circle.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_open.png b/commons/src/main/res/drawable-xxhdpi/ic_open.png
new file mode 100644
index 0000000..5fc50ad
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_open.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_orientation_auto.png b/commons/src/main/res/drawable-xxhdpi/ic_orientation_auto.png
new file mode 100644
index 0000000..37bad63
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_orientation_auto.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_orientation_landscape.png b/commons/src/main/res/drawable-xxhdpi/ic_orientation_landscape.png
new file mode 100644
index 0000000..0eff52c
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_orientation_landscape.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_orientation_portrait.png b/commons/src/main/res/drawable-xxhdpi/ic_orientation_portrait.png
new file mode 100644
index 0000000..0df5c58
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_orientation_portrait.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_pause.png b/commons/src/main/res/drawable-xxhdpi/ic_pause.png
new file mode 100644
index 0000000..ffd9061
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_pause.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_person.png b/commons/src/main/res/drawable-xxhdpi/ic_person.png
new file mode 100644
index 0000000..bc48e51
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_person.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_phone.png b/commons/src/main/res/drawable-xxhdpi/ic_phone.png
new file mode 100644
index 0000000..8bdea6c
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_phone.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_place.png b/commons/src/main/res/drawable-xxhdpi/ic_place.png
new file mode 100644
index 0000000..9f973cf
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_place.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_place_big.png b/commons/src/main/res/drawable-xxhdpi/ic_place_big.png
new file mode 100644
index 0000000..633bc56
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_place_big.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_play.png b/commons/src/main/res/drawable-xxhdpi/ic_play.png
new file mode 100644
index 0000000..8e6b3d1
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_play.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_plus.png b/commons/src/main/res/drawable-xxhdpi/ic_plus.png
new file mode 100644
index 0000000..b97c0e2
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_plus.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_properties.png b/commons/src/main/res/drawable-xxhdpi/ic_properties.png
new file mode 100644
index 0000000..db51fc7
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_properties.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_redo.png b/commons/src/main/res/drawable-xxhdpi/ic_redo.png
new file mode 100644
index 0000000..2a52d3b
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_redo.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_redo_big.png b/commons/src/main/res/drawable-xxhdpi/ic_redo_big.png
new file mode 100644
index 0000000..df697d2
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_redo_big.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_rename_new.png b/commons/src/main/res/drawable-xxhdpi/ic_rename_new.png
new file mode 100644
index 0000000..15953ad
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_rename_new.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_repeat.png b/commons/src/main/res/drawable-xxhdpi/ic_repeat.png
new file mode 100644
index 0000000..74eaa0c
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_repeat.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_reset.png b/commons/src/main/res/drawable-xxhdpi/ic_reset.png
new file mode 100644
index 0000000..0f35747
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_reset.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_save.png b/commons/src/main/res/drawable-xxhdpi/ic_save.png
new file mode 100644
index 0000000..3e0ce1a
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_save.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_sd_card.png b/commons/src/main/res/drawable-xxhdpi/ic_sd_card.png
new file mode 100644
index 0000000..56aaae9
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_sd_card.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_search.png b/commons/src/main/res/drawable-xxhdpi/ic_search.png
new file mode 100644
index 0000000..abbb989
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_search.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_select_all.png b/commons/src/main/res/drawable-xxhdpi/ic_select_all.png
new file mode 100644
index 0000000..162ab98
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_select_all.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_share.png b/commons/src/main/res/drawable-xxhdpi/ic_share.png
new file mode 100644
index 0000000..a35b3cd
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_share.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_sms.png b/commons/src/main/res/drawable-xxhdpi/ic_sms.png
new file mode 100644
index 0000000..a3d45a5
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_sms.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_sms_big.png b/commons/src/main/res/drawable-xxhdpi/ic_sms_big.png
new file mode 100644
index 0000000..44f7219
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_sms_big.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_snooze.png b/commons/src/main/res/drawable-xxhdpi/ic_snooze.png
new file mode 100644
index 0000000..1761d04
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_snooze.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_snooze_big.png b/commons/src/main/res/drawable-xxhdpi/ic_snooze_big.png
new file mode 100644
index 0000000..07da471
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_snooze_big.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_sort.png b/commons/src/main/res/drawable-xxhdpi/ic_sort.png
new file mode 100644
index 0000000..42cf976
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_sort.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_star_off.png b/commons/src/main/res/drawable-xxhdpi/ic_star_off.png
new file mode 100644
index 0000000..7e41906
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_star_off.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_star_off_big.png b/commons/src/main/res/drawable-xxhdpi/ic_star_off_big.png
new file mode 100644
index 0000000..25ef834
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_star_off_big.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_star_on.png b/commons/src/main/res/drawable-xxhdpi/ic_star_on.png
new file mode 100644
index 0000000..aa58792
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_star_on.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_star_on_big.png b/commons/src/main/res/drawable-xxhdpi/ic_star_on_big.png
new file mode 100644
index 0000000..66dc391
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_star_on_big.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_stop.png b/commons/src/main/res/drawable-xxhdpi/ic_stop.png
new file mode 100644
index 0000000..ac7b552
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_stop.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_undo.png b/commons/src/main/res/drawable-xxhdpi/ic_undo.png
new file mode 100644
index 0000000..a3531a5
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_undo.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_undo_big.png b/commons/src/main/res/drawable-xxhdpi/ic_undo_big.png
new file mode 100644
index 0000000..a47587c
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_undo_big.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_unhide.png b/commons/src/main/res/drawable-xxhdpi/ic_unhide.png
new file mode 100644
index 0000000..196ab01
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_unhide.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_usb.png b/commons/src/main/res/drawable-xxhdpi/ic_usb.png
new file mode 100644
index 0000000..bfc2415
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_usb.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/ic_vibrate.png b/commons/src/main/res/drawable-xxhdpi/ic_vibrate.png
new file mode 100644
index 0000000..a0fa587
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/ic_vibrate.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/img_write_storage.png b/commons/src/main/res/drawable-xxhdpi/img_write_storage.png
new file mode 100644
index 0000000..8152e13
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/img_write_storage.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/img_write_storage_otg.png b/commons/src/main/res/drawable-xxhdpi/img_write_storage_otg.png
new file mode 100644
index 0000000..c75fa60
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/img_write_storage_otg.png differ
diff --git a/commons/src/main/res/drawable-xxhdpi/img_write_storage_sd.png b/commons/src/main/res/drawable-xxhdpi/img_write_storage_sd.png
new file mode 100644
index 0000000..18f2063
Binary files /dev/null and b/commons/src/main/res/drawable-xxhdpi/img_write_storage_sd.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_arrow_right.png b/commons/src/main/res/drawable-xxxhdpi/ic_arrow_right.png
new file mode 100644
index 0000000..5e93f88
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_arrow_right.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_attach_file.png b/commons/src/main/res/drawable-xxxhdpi/ic_attach_file.png
new file mode 100644
index 0000000..771425c
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_attach_file.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_bell.png b/commons/src/main/res/drawable-xxxhdpi/ic_bell.png
new file mode 100644
index 0000000..10f0c05
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_bell.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_camera.png b/commons/src/main/res/drawable-xxxhdpi/ic_camera.png
new file mode 100644
index 0000000..777658e
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_camera.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_check.png b/commons/src/main/res/drawable-xxxhdpi/ic_check.png
new file mode 100644
index 0000000..d670618
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_check.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_chevron_right.png b/commons/src/main/res/drawable-xxxhdpi/ic_chevron_right.png
new file mode 100644
index 0000000..962a8d6
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_chevron_right.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_circle_filled.png b/commons/src/main/res/drawable-xxxhdpi/ic_circle_filled.png
new file mode 100644
index 0000000..bbd71f5
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_circle_filled.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_clock.png b/commons/src/main/res/drawable-xxxhdpi/ic_clock.png
new file mode 100644
index 0000000..f156937
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_clock.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_copy.png b/commons/src/main/res/drawable-xxxhdpi/ic_copy.png
new file mode 100644
index 0000000..4ddee9e
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_copy.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_cross.png b/commons/src/main/res/drawable-xxxhdpi/ic_cross.png
new file mode 100644
index 0000000..47eebc9
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_cross.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_delete.png b/commons/src/main/res/drawable-xxxhdpi/ic_delete.png
new file mode 100644
index 0000000..8d322aa
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_delete.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_edit.png b/commons/src/main/res/drawable-xxxhdpi/ic_edit.png
new file mode 100644
index 0000000..d6668a0
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_edit.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_email.png b/commons/src/main/res/drawable-xxxhdpi/ic_email.png
new file mode 100644
index 0000000..6947be0
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_email.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_email_big.png b/commons/src/main/res/drawable-xxxhdpi/ic_email_big.png
new file mode 100644
index 0000000..309263b
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_email_big.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_facebook.png b/commons/src/main/res/drawable-xxxhdpi/ic_facebook.png
new file mode 100644
index 0000000..d8b3624
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_facebook.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_file.png b/commons/src/main/res/drawable-xxxhdpi/ic_file.png
new file mode 100644
index 0000000..5bd5690
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_file.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_filter.png b/commons/src/main/res/drawable-xxxhdpi/ic_filter.png
new file mode 100644
index 0000000..cb2207f
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_filter.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_fingerprint.png b/commons/src/main/res/drawable-xxxhdpi/ic_fingerprint.png
new file mode 100644
index 0000000..1b16dc2
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_fingerprint.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_folder.png b/commons/src/main/res/drawable-xxxhdpi/ic_folder.png
new file mode 100644
index 0000000..a1afbe9
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_folder.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_gplus.png b/commons/src/main/res/drawable-xxxhdpi/ic_gplus.png
new file mode 100644
index 0000000..932c51a
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_gplus.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_hide.png b/commons/src/main/res/drawable-xxxhdpi/ic_hide.png
new file mode 100644
index 0000000..3e7242a
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_hide.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_info.png b/commons/src/main/res/drawable-xxxhdpi/ic_info.png
new file mode 100644
index 0000000..3a82cab
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_info.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_label.png b/commons/src/main/res/drawable-xxxhdpi/ic_label.png
new file mode 100644
index 0000000..dde0fc7
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_label.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_minus.png b/commons/src/main/res/drawable-xxxhdpi/ic_minus.png
new file mode 100644
index 0000000..8a0f2e1
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_minus.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_minus_circle.png b/commons/src/main/res/drawable-xxxhdpi/ic_minus_circle.png
new file mode 100644
index 0000000..2433f68
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_minus_circle.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_open.png b/commons/src/main/res/drawable-xxxhdpi/ic_open.png
new file mode 100644
index 0000000..dea54e4
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_open.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_orientation_auto.png b/commons/src/main/res/drawable-xxxhdpi/ic_orientation_auto.png
new file mode 100644
index 0000000..711a7e2
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_orientation_auto.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_orientation_landscape.png b/commons/src/main/res/drawable-xxxhdpi/ic_orientation_landscape.png
new file mode 100644
index 0000000..231311a
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_orientation_landscape.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_orientation_portrait.png b/commons/src/main/res/drawable-xxxhdpi/ic_orientation_portrait.png
new file mode 100644
index 0000000..cdfbb16
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_orientation_portrait.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_pause.png b/commons/src/main/res/drawable-xxxhdpi/ic_pause.png
new file mode 100644
index 0000000..fe83ca5
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_pause.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_person.png b/commons/src/main/res/drawable-xxxhdpi/ic_person.png
new file mode 100644
index 0000000..6a14714
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_person.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_phone.png b/commons/src/main/res/drawable-xxxhdpi/ic_phone.png
new file mode 100644
index 0000000..a8e295a
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_phone.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_place.png b/commons/src/main/res/drawable-xxxhdpi/ic_place.png
new file mode 100644
index 0000000..85e4caa
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_place.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_place_big.png b/commons/src/main/res/drawable-xxxhdpi/ic_place_big.png
new file mode 100644
index 0000000..42ab08c
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_place_big.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_play.png b/commons/src/main/res/drawable-xxxhdpi/ic_play.png
new file mode 100644
index 0000000..2745c3a
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_play.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_plus.png b/commons/src/main/res/drawable-xxxhdpi/ic_plus.png
new file mode 100644
index 0000000..2bef059
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_plus.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_properties.png b/commons/src/main/res/drawable-xxxhdpi/ic_properties.png
new file mode 100644
index 0000000..3a82cab
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_properties.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_redo.png b/commons/src/main/res/drawable-xxxhdpi/ic_redo.png
new file mode 100644
index 0000000..1725bab
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_redo.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_redo_big.png b/commons/src/main/res/drawable-xxxhdpi/ic_redo_big.png
new file mode 100644
index 0000000..3999f34
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_redo_big.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_rename_new.png b/commons/src/main/res/drawable-xxxhdpi/ic_rename_new.png
new file mode 100644
index 0000000..70c5ba7
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_rename_new.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_repeat.png b/commons/src/main/res/drawable-xxxhdpi/ic_repeat.png
new file mode 100644
index 0000000..f5beca2
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_repeat.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_reset.png b/commons/src/main/res/drawable-xxxhdpi/ic_reset.png
new file mode 100644
index 0000000..74076b6
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_reset.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_save.png b/commons/src/main/res/drawable-xxxhdpi/ic_save.png
new file mode 100644
index 0000000..bd80bf1
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_save.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_sd_card.png b/commons/src/main/res/drawable-xxxhdpi/ic_sd_card.png
new file mode 100644
index 0000000..b87644a
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_sd_card.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_search.png b/commons/src/main/res/drawable-xxxhdpi/ic_search.png
new file mode 100644
index 0000000..dd5adfc
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_search.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_select_all.png b/commons/src/main/res/drawable-xxxhdpi/ic_select_all.png
new file mode 100644
index 0000000..896e1ac
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_select_all.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_share.png b/commons/src/main/res/drawable-xxxhdpi/ic_share.png
new file mode 100644
index 0000000..e351c7b
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_share.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_sms.png b/commons/src/main/res/drawable-xxxhdpi/ic_sms.png
new file mode 100644
index 0000000..4f23ef5
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_sms.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_sms_big.png b/commons/src/main/res/drawable-xxxhdpi/ic_sms_big.png
new file mode 100644
index 0000000..4c240ba
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_sms_big.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_snooze.png b/commons/src/main/res/drawable-xxxhdpi/ic_snooze.png
new file mode 100644
index 0000000..c3cdb92
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_snooze.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_snooze_big.png b/commons/src/main/res/drawable-xxxhdpi/ic_snooze_big.png
new file mode 100644
index 0000000..bbcfd5a
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_snooze_big.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_sort.png b/commons/src/main/res/drawable-xxxhdpi/ic_sort.png
new file mode 100644
index 0000000..f2454f1
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_sort.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_star_off.png b/commons/src/main/res/drawable-xxxhdpi/ic_star_off.png
new file mode 100644
index 0000000..0bae0bd
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_star_off.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_star_off_big.png b/commons/src/main/res/drawable-xxxhdpi/ic_star_off_big.png
new file mode 100644
index 0000000..5a82e2e
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_star_off_big.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_star_on.png b/commons/src/main/res/drawable-xxxhdpi/ic_star_on.png
new file mode 100644
index 0000000..58d71b3
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_star_on.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_star_on_big.png b/commons/src/main/res/drawable-xxxhdpi/ic_star_on_big.png
new file mode 100644
index 0000000..74b1c0b
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_star_on_big.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_stop.png b/commons/src/main/res/drawable-xxxhdpi/ic_stop.png
new file mode 100644
index 0000000..4741d31
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_stop.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_undo.png b/commons/src/main/res/drawable-xxxhdpi/ic_undo.png
new file mode 100644
index 0000000..562e11b
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_undo.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_undo_big.png b/commons/src/main/res/drawable-xxxhdpi/ic_undo_big.png
new file mode 100644
index 0000000..9959132
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_undo_big.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_unhide.png b/commons/src/main/res/drawable-xxxhdpi/ic_unhide.png
new file mode 100644
index 0000000..8382da2
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_unhide.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_usb.png b/commons/src/main/res/drawable-xxxhdpi/ic_usb.png
new file mode 100644
index 0000000..f0a7e8a
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_usb.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/ic_vibrate.png b/commons/src/main/res/drawable-xxxhdpi/ic_vibrate.png
new file mode 100644
index 0000000..95c0ad2
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/ic_vibrate.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/img_write_storage.png b/commons/src/main/res/drawable-xxxhdpi/img_write_storage.png
new file mode 100644
index 0000000..1b73b1d
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/img_write_storage.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/img_write_storage_otg.png b/commons/src/main/res/drawable-xxxhdpi/img_write_storage_otg.png
new file mode 100644
index 0000000..aa2c12c
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/img_write_storage_otg.png differ
diff --git a/commons/src/main/res/drawable-xxxhdpi/img_write_storage_sd.png b/commons/src/main/res/drawable-xxxhdpi/img_write_storage_sd.png
new file mode 100644
index 0000000..38b89f7
Binary files /dev/null and b/commons/src/main/res/drawable-xxxhdpi/img_write_storage_sd.png differ
diff --git a/commons/src/main/res/drawable/button_background.9.png b/commons/src/main/res/drawable/button_background.9.png
new file mode 100644
index 0000000..f238161
Binary files /dev/null and b/commons/src/main/res/drawable/button_background.9.png differ
diff --git a/commons/src/main/res/drawable/circle_background.xml b/commons/src/main/res/drawable/circle_background.xml
new file mode 100644
index 0000000..58f030d
--- /dev/null
+++ b/commons/src/main/res/drawable/circle_background.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/commons/src/main/res/drawable/color_picker_circle.xml b/commons/src/main/res/drawable/color_picker_circle.xml
new file mode 100644
index 0000000..566473c
--- /dev/null
+++ b/commons/src/main/res/drawable/color_picker_circle.xml
@@ -0,0 +1,28 @@
+
+
+ -
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/drawable/divider.xml b/commons/src/main/res/drawable/divider.xml
new file mode 100644
index 0000000..50b3dc4
--- /dev/null
+++ b/commons/src/main/res/drawable/divider.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/drawable/fastscroller_bubble.xml b/commons/src/main/res/drawable/fastscroller_bubble.xml
new file mode 100644
index 0000000..1129287
--- /dev/null
+++ b/commons/src/main/res/drawable/fastscroller_bubble.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/drawable/fastscroller_handle_horizontal.xml b/commons/src/main/res/drawable/fastscroller_handle_horizontal.xml
new file mode 100644
index 0000000..e152e8a
--- /dev/null
+++ b/commons/src/main/res/drawable/fastscroller_handle_horizontal.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/drawable/fastscroller_handle_vertical.xml b/commons/src/main/res/drawable/fastscroller_handle_vertical.xml
new file mode 100644
index 0000000..0650c56
--- /dev/null
+++ b/commons/src/main/res/drawable/fastscroller_handle_vertical.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/drawable/selector.xml b/commons/src/main/res/drawable/selector.xml
new file mode 100644
index 0000000..c7e60e3
--- /dev/null
+++ b/commons/src/main/res/drawable/selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/commons/src/main/res/drawable/transparent_button.xml b/commons/src/main/res/drawable/transparent_button.xml
new file mode 100644
index 0000000..d52b4ed
--- /dev/null
+++ b/commons/src/main/res/drawable/transparent_button.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/commons/src/main/res/drawable/transparent_button_pressed.xml b/commons/src/main/res/drawable/transparent_button_pressed.xml
new file mode 100644
index 0000000..e6fe04b
--- /dev/null
+++ b/commons/src/main/res/drawable/transparent_button_pressed.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/commons/src/main/res/layout/actionbar_title.xml b/commons/src/main/res/layout/actionbar_title.xml
new file mode 100644
index 0000000..12ed123
--- /dev/null
+++ b/commons/src/main/res/layout/actionbar_title.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/commons/src/main/res/layout/activity_about.xml b/commons/src/main/res/layout/activity_about.xml
new file mode 100644
index 0000000..ea62787
--- /dev/null
+++ b/commons/src/main/res/layout/activity_about.xml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/activity_customization.xml b/commons/src/main/res/layout/activity_customization.xml
new file mode 100644
index 0000000..65c1ec5
--- /dev/null
+++ b/commons/src/main/res/layout/activity_customization.xml
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/activity_faq.xml b/commons/src/main/res/layout/activity_faq.xml
new file mode 100644
index 0000000..a3b1605
--- /dev/null
+++ b/commons/src/main/res/layout/activity_faq.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/activity_license.xml b/commons/src/main/res/layout/activity_license.xml
new file mode 100644
index 0000000..889c7ee
--- /dev/null
+++ b/commons/src/main/res/layout/activity_license.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/breadcrumb_item.xml b/commons/src/main/res/layout/breadcrumb_item.xml
new file mode 100644
index 0000000..b9de6c3
--- /dev/null
+++ b/commons/src/main/res/layout/breadcrumb_item.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/commons/src/main/res/layout/dialog_color_picker.xml b/commons/src/main/res/layout/dialog_color_picker.xml
new file mode 100644
index 0000000..c3e3cfd
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_color_picker.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_create_new_folder.xml b/commons/src/main/res/layout/dialog_create_new_folder.xml
new file mode 100644
index 0000000..ac0ac22
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_create_new_folder.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_custom_interval_picker.xml b/commons/src/main/res/layout/dialog_custom_interval_picker.xml
new file mode 100644
index 0000000..b26c4e5
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_custom_interval_picker.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_file_conflict.xml b/commons/src/main/res/layout/dialog_file_conflict.xml
new file mode 100644
index 0000000..35c30a6
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_file_conflict.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_filepicker.xml b/commons/src/main/res/layout/dialog_filepicker.xml
new file mode 100644
index 0000000..7691c5e
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_filepicker.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_line_color_picker.xml b/commons/src/main/res/layout/dialog_line_color_picker.xml
new file mode 100644
index 0000000..e514cba
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_line_color_picker.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_message.xml b/commons/src/main/res/layout/dialog_message.xml
new file mode 100644
index 0000000..7062bc0
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_message.xml
@@ -0,0 +1,11 @@
+
+
diff --git a/commons/src/main/res/layout/dialog_properties.xml b/commons/src/main/res/layout/dialog_properties.xml
new file mode 100644
index 0000000..0467537
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_properties.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_purchase_thank_you.xml b/commons/src/main/res/layout/dialog_purchase_thank_you.xml
new file mode 100644
index 0000000..2395d69
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_purchase_thank_you.xml
@@ -0,0 +1,13 @@
+
+
diff --git a/commons/src/main/res/layout/dialog_radio_group.xml b/commons/src/main/res/layout/dialog_radio_group.xml
new file mode 100644
index 0000000..0da0e43
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_radio_group.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_rename_item.xml b/commons/src/main/res/layout/dialog_rename_item.xml
new file mode 100644
index 0000000..02773a6
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_rename_item.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_security.xml b/commons/src/main/res/layout/dialog_security.xml
new file mode 100644
index 0000000..0ca6d5e
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_security.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_select_alarm_sound.xml b/commons/src/main/res/layout/dialog_select_alarm_sound.xml
new file mode 100644
index 0000000..00bce19
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_select_alarm_sound.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_textview.xml b/commons/src/main/res/layout/dialog_textview.xml
new file mode 100644
index 0000000..9d2ea09
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_textview.xml
@@ -0,0 +1,10 @@
+
+
diff --git a/commons/src/main/res/layout/dialog_title.xml b/commons/src/main/res/layout/dialog_title.xml
new file mode 100644
index 0000000..1ece13a
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_title.xml
@@ -0,0 +1,13 @@
+
+
diff --git a/commons/src/main/res/layout/dialog_whats_new.xml b/commons/src/main/res/layout/dialog_whats_new.xml
new file mode 100644
index 0000000..964aeb5
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_whats_new.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_write_permission.xml b/commons/src/main/res/layout/dialog_write_permission.xml
new file mode 100644
index 0000000..0a5e1d2
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_write_permission.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/dialog_write_permission_otg.xml b/commons/src/main/res/layout/dialog_write_permission_otg.xml
new file mode 100644
index 0000000..5e7f4e1
--- /dev/null
+++ b/commons/src/main/res/layout/dialog_write_permission_otg.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/divider.xml b/commons/src/main/res/layout/divider.xml
new file mode 100644
index 0000000..7ca11b7
--- /dev/null
+++ b/commons/src/main/res/layout/divider.xml
@@ -0,0 +1,6 @@
+
+
diff --git a/commons/src/main/res/layout/empty_image_view.xml b/commons/src/main/res/layout/empty_image_view.xml
new file mode 100644
index 0000000..baf0890
--- /dev/null
+++ b/commons/src/main/res/layout/empty_image_view.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/commons/src/main/res/layout/fastscroller_handle_horizontal.xml b/commons/src/main/res/layout/fastscroller_handle_horizontal.xml
new file mode 100644
index 0000000..3417edc
--- /dev/null
+++ b/commons/src/main/res/layout/fastscroller_handle_horizontal.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/fastscroller_handle_vertical.xml b/commons/src/main/res/layout/fastscroller_handle_vertical.xml
new file mode 100644
index 0000000..c47fb2c
--- /dev/null
+++ b/commons/src/main/res/layout/fastscroller_handle_vertical.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/filepicker_list_item.xml b/commons/src/main/res/layout/filepicker_list_item.xml
new file mode 100644
index 0000000..8ce4739
--- /dev/null
+++ b/commons/src/main/res/layout/filepicker_list_item.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/item_select_alarm_sound.xml b/commons/src/main/res/layout/item_select_alarm_sound.xml
new file mode 100644
index 0000000..fc82cba
--- /dev/null
+++ b/commons/src/main/res/layout/item_select_alarm_sound.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/commons/src/main/res/layout/license_faq_item.xml b/commons/src/main/res/layout/license_faq_item.xml
new file mode 100644
index 0000000..1f6694d
--- /dev/null
+++ b/commons/src/main/res/layout/license_faq_item.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/property_item.xml b/commons/src/main/res/layout/property_item.xml
new file mode 100644
index 0000000..c108fde
--- /dev/null
+++ b/commons/src/main/res/layout/property_item.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/radio_button.xml b/commons/src/main/res/layout/radio_button.xml
new file mode 100644
index 0000000..cedee9a
--- /dev/null
+++ b/commons/src/main/res/layout/radio_button.xml
@@ -0,0 +1,8 @@
+
+
diff --git a/commons/src/main/res/layout/tab_pattern.xml b/commons/src/main/res/layout/tab_pattern.xml
new file mode 100644
index 0000000..b1ab9ba
--- /dev/null
+++ b/commons/src/main/res/layout/tab_pattern.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/layout/tab_pin.xml b/commons/src/main/res/layout/tab_pin.xml
new file mode 100644
index 0000000..6a2a0b0
--- /dev/null
+++ b/commons/src/main/res/layout/tab_pin.xml
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/menu/cab_delete_only.xml b/commons/src/main/res/menu/cab_delete_only.xml
new file mode 100644
index 0000000..af34568
--- /dev/null
+++ b/commons/src/main/res/menu/cab_delete_only.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/commons/src/main/res/menu/cab_remove_only.xml b/commons/src/main/res/menu/cab_remove_only.xml
new file mode 100644
index 0000000..bc4ee98
--- /dev/null
+++ b/commons/src/main/res/menu/cab_remove_only.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/commons/src/main/res/menu/menu_customization.xml b/commons/src/main/res/menu/menu_customization.xml
new file mode 100644
index 0000000..82547aa
--- /dev/null
+++ b/commons/src/main/res/menu/menu_customization.xml
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/commons/src/main/res/values-ar/strings.xml b/commons/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..cd3d7d2
--- /dev/null
+++ b/commons/src/main/res/values-ar/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Cancel
+ Save as
+ File saved successfully
+ Invalid file format
+ Out of memory error
+ An error occurred: %s
+ Open with
+ No valid app found
+ Set as
+ Value copied to clipboard
+ Unknown
+ Always
+ Never
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ المفضلة
+ Add favorites
+ اضافة الى المفضلة
+ حذف من المفضلة
+
+
+ Search
+ Type in at least 2 characters to start the search.
+
+
+ Filter
+ No items found.
+ Change filter
+
+
+ Storage permission is required
+ Contacts permission is required
+ Camera permission is required
+ Audio permission is required
+
+
+ Rename file
+ Rename folder
+ Could not rename the file
+ Could not rename the folder
+ Folder name must not be empty
+ A folder with that name already exists
+ Cannot rename the root folder of a storage
+ Folder renamed successfully
+ Renaming folder
+ Filename cannot be empty
+ Filename contains invalid characters
+ Filename \'%s\' contains invalid characters
+ Extension cannot be empty
+ Source file %s doesn\'t exist
+
+
+ Copy
+ Move
+ Copy / Move
+ Copy to
+ Move to
+ Source
+ Destination
+ Select destination
+ Click here to select destination
+ Could not write to the selected destination
+ Please select a destination
+ Source and destination cannot be the same
+ Could not copy the files
+ Copying…
+ Files copied successfully
+ An error occurred
+ Moving…
+ Files moved successfully
+ Some files could not be moved
+ Some files could not be copied
+ No files selected
+ Saving…
+ Could not create folder %s
+ Could not create file %s
+
+
+ Create new
+ Folder
+ File
+ Create new folder
+ A file or folder with that name already exists
+ The name contains invalid characters
+ Please enter a name
+ An unknown error occurred
+
+
+ File \"%s\" already exists
+ File \"%s\" already exists. Overwrite?
+ Folder \"%s\" already exists
+ Merge
+ Overwrite
+ Skip
+ Append with \'_1\'
+ Apply to all
+
+
+ Select a folder
+ Select a file
+ Confirm external storage access
+ Please choose the root folder of the SD card on the next screen, to grant write access
+ If you don\'t see the SD card, try this
+ Confirm selection
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Select storage
+ Internal
+ SD Card
+ Root
+ Wrong folder selected, please select the root folder of your SD card
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Properties
+ Path
+ Items selected
+ Direct children count
+ Total files count
+ Resolution
+ Duration
+ Artist
+ Album
+ Focal length
+ Exposure time
+ ISO speed
+ F-number
+ Camera
+ EXIF
+ Song title
+
+
+ Background color
+ Text color
+ Primary color
+ Foreground color
+ App icon color
+ Restore defaults
+ Change color
+ Theme
+ Changing a color will make it switch to Custom theme
+ Save
+ Discard
+ Undo changes
+ Are you sure you want to undo your changes?
+ You have unsaved changes. Save before exit?
+ Apply colors to all Simple Apps
+ Colors updated successfully. A new Theme called \'Shared\' has been added, please use that for updating all app colors in the future.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Light
+ Dark
+ Solarized
+ Dark red
+ Black & White
+ Custom
+ Shared
+
+
+ What\'s new
+ * only the bigger updates are listed here, there are always some smaller improvements too
+
+
+ Delete
+ Remove
+ Rename
+ Share
+ Share via
+ Select all
+ Hide
+ Unhide
+ Hide folder
+ Unhide folder
+ Temporarily show hidden
+ Stop showing hidden media
+ You cannot share this much content at once
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sort by
+ Name
+ Size
+ Last modified
+ Date taken
+ Title
+ Filename
+ Extension
+ Ascending
+ Descending
+ Use for this folder only
+
+
+ Are you sure you want to proceed with the deletion?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item in the Recycle Bin?
+ Do not ask again in this session
+ Yes
+ No
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Enter PIN
+ Please enter a PIN
+ Wrong PIN
+ Repeat PIN
+ Pattern
+ Insert pattern
+ Wrong pattern
+ Repeat pattern
+ Fingerprint
+ Add fingerprint
+ Please place your finger on the fingerprint sensor
+ Authentication failed
+ Authentication blocked, please try again in a moment
+ You have no fingerprints registered, please add some in the Settings of your device
+ Go to Settings
+ Password setup successfully. Please reinstall the app in case you forget it.
+ Protection setup successfully. Please reinstall the app in case of problems with reseting it.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minutes
+ hours
+ days
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+ - %d day
+ - %d days
+
+
+ - %d week
+ - %d weeks
+
+
+ - %d month
+ - %d months
+
+
+ - %d year
+ - %d years
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d minute before
+ - %d minutes before
+
+
+ - %d hour before
+ - %d hours before
+
+
+ - %d day before
+ - %d days before
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ No reminder
+ At start
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Settings
+ Purchase Simple Thank You
+ Customize colors
+ Customize widget colors
+ Use English language
+ Avoid showing What\'s New on startup
+ Show hidden items
+ Font size
+ Small
+ Medium
+ Large
+ Extra large
+ Password protect hidden item visibility
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ File operations
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importing…
+ Exporting…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ January
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ Saturday
+ Sunday
+
+ M
+ T
+ W
+ T
+ F
+ S
+ S
+
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+
+
+ About
+ For the source codes visit
+ Send your feedback or suggestions to
+ More apps
+ Third party licences
+ Invite friends
+ Hey, come check out %1$s at %2$s
+ Invite via
+ Rate us
+ Donate
+ Donate
+ Follow us
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Additional info
+ App version: %s
+ Device OS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ This app uses the following third party libraries to make my life easier. Thank you.
+ Third party licences
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-az/strings.xml b/commons/src/main/res/values-az/strings.xml
new file mode 100644
index 0000000..929b200
--- /dev/null
+++ b/commons/src/main/res/values-az/strings.xml
@@ -0,0 +1,561 @@
+
+ Oldu
+ Ləğv Et
+ Saxla
+ Fayl uğurla saxlandı
+ Keçərsiz fayl formatı
+ Yaddaş doludur xətası
+ Xəta baş verdi: %s
+ Bununla aç
+ Etibarlı tətbiq tapılmadı
+ Təyin et
+ Miqdar panelə kopyalandı
+ Bilinməyən
+ Həmişə
+ Heç vaxt
+ Detallar
+ Qeydlər
+ \'%s\' qovluğu silinir
+ None
+ Label
+
+
+ Sevimlilər
+ Sevimlilər əlavə et
+ Sevimlilərə əlavə et
+ Sevimlilərdən sil
+
+
+ Axtar
+ Axtarışa başlamaq üçün ən az 2 simvol yazın.
+
+
+ Filtr
+ Heçnə tapılmadı.
+ Filtri dəyiş
+
+
+ Yaddaş icazəsi tələb olunur
+ Kontakt icazəsi tələb olunur
+ Kamera icazəsi tələb olunur
+ Səs icazəsi tələb olunur
+
+
+ Faylı yenidən adlandır
+ Qovluğu yenidən adlandır
+ Faylı adlandırmaq olmur
+ Qovluğu adlandırmaq olmur
+ Qovluq adı boş ola bilməz
+ Bu adda qovluq artıq mövcuddur
+ Yaddaşın root qovluğunu adlandırmaq olmur
+ Qovluq uğurla adlandırıldı
+ Qovluq adlandırma
+ Fayl adı boş ola bilməz
+ Faylın adında etibarsız simvollar var
+ Fayl adı \'%s\' etibarsız simvollar ehtiva edir
+ Uzantı boş ola bilməz
+ Kök faylı %s yoxdur
+
+
+ Kopyala
+ Yerini dəyiş
+ Kopyala / Yerini dəyiş
+ Bura kopyala
+ Yerini bura dəyiş
+ Kök
+ Hədəf
+ Hədəf seç
+ Hədəf seçmək üçün bura bas
+ Seçilmiş hədəfə yazmaq olmur
+ Xahiş olunur hədəf seçin
+ Kök və hədəf eyni ola bilməz
+ Faylları kopyalamaq olmur
+ Kopyalanır…
+ Fayl uğurla kopyalandı
+ Xəta baş verdi
+ Yerini dəyişir…
+ Faylın yeri uğurla dəyişdirildi
+ Bəzi faylların yeri dəyişdirilmir
+ Bəzi faylları kopyalamaq olmur
+ Fayl seçilməyib
+ Saxlanılır…
+ %s qovluğunu yaratmaq olmur
+ %s faylını yaratmaq olmur
+
+
+ Yeni yarat
+ Qovluq
+ Fayl
+ Yeni qovluq yarat
+ Eyni adda fayl və ya qovluq artıq mövcuddur
+ Ad etibarsız simvollar ehtiva edir
+ Zəhmət olmasa adı daxil edin
+ Bilinməyən xəta baş verdi
+
+
+ \"%s\" faylı artıq mövcuddur
+ \"%s\" faylı artıq mövcuddur. Üzərinə yazılsın?
+ \"%s\" qovluğu artıq mövcuddur
+ Birləşdir
+ Üzərinə yaz
+ Keç
+ \'_1\' ilə əlavə et
+ Hamsına şamil et
+
+
+ Qovluq seç
+ Fayl seç
+ Xarici yaddaş icazəsini təsdiqlə
+ Xahiş olunur icazəni təsdiqləmək üçün gələcək ekranda SD kartın root qovluğunu seçin.
+ Əgər SD kart görünmürsə, bunu yoxlayın
+ Seçimi təsdiqlə
+
+
+ - %d fayl
+ - %d fayl
+
+
+
+
+ - %d fayl
+ - %d fayl
+
+
+
+ - %d fayl silinir
+ - %d fayl silinir
+
+
+
+ Yaddaş seç
+ Daxili
+ SD Kart
+ Root
+ Seçilən qovluq yanlışdır, xahiş olunur SD kartın root qovluğunu seçin
+ SD kart və OTG cihaz yolu eyni ola bilməz
+ Görünür tətbiqi SD karta yükləmisiniz, bu widgetlərin işləməsinin qarşısını alır. Siz widgetləri əlçatan widgetlər siyahısında belə görə bilməyəcəksiniz.
+ Bu sistem məhdudiyyətidir, əgər widgetlərin işləməsini istəyirsinizsə, tətbiqi daxili yaddaşa köçürməlisiniz.
+
+
+ Xüsusiyyətlər
+ Yol
+ Fayl seçildi
+ Alt qovluq sayı
+ Bütün faylların sayı
+ Görüntü imkanı
+ Uzunluq
+ Müğənni
+ Albom
+ Fokus məsafəsi
+ Poza vaxtı
+ ISO sürəti
+ Diyafram
+ Kamera
+ EXIF
+ Musiqi başlığı
+
+
+ Arxaplan rəngi
+ Yazı rəngi
+ Birinci rəng
+ Önplan rəngi
+ Tətbiq ikonu rəngi
+ Standarta qaytar
+ Rəngi dəyiş
+ Mövzu
+ Rəngi dəyişmək onu Özəl mövzuya çevirəcək
+ Saxla
+ Yoxsay
+ Dəyişiklikləri geri al
+ Dəyişiklikləri geri almaq istədiyinizə əminsiniz?
+ Yadda saxlamadığınız dəyişikliklər var. Çıxmazdan əvvəl saxlanılsın?
+ Rəngləri bütün Simple Apps tətbiqlərinə şamil et
+ Rənglər uğurla yeniləndi. \'Paylanmış\' adlı yeni tema əlavə edildi, xahiş olunur gələcəkdə bütün tətbiqlərin yeniləmələri üçün bundan istifadə edin.
+
+ Bu funksiyanı açdığınız və təkmilləşdirməni dəstəklədiyiniz üçün Sadə Təşəkkür. Çox Sağolun!
+ ]]>
+
+
+
+ İşıqlı
+ Qaranlıq
+ Közərmiş
+ Qaraqırmızı
+ Ağ və Qara
+ Özəl
+ Paylanmış
+
+
+ Yeni nə var
+ * burada yalnız böyük təzələmələr sıralanıb, həmişə əlavə yeniliklər də olur
+
+
+ Sil
+ Sil
+ Yenidən adlandır
+ Paylaş
+ Paylaş
+ Hamsını seç
+ Gizlət
+ Gizlətmə
+ Qovluğu gizlət
+ Qovluğu gizlətmə
+ Gizlənmişi müvəqqəti göstər
+ Gizlənmiş mediyanı göstərmə
+ Bu qədər faylı bir dəfəyə paylaşa bilməzsiniz
+ Zibil qutusunu boşalt və qapat
+ Geri al
+ Təkrar et
+
+
+ Sırala
+ Ad
+ Ölçü
+ Son dəyişdirilən
+ Götürülmə tarixi
+ Başlıq
+ Fayl adı
+ Fayl çıxışı
+ Artan
+ Azalan
+ Yalnız bu qovluq üçün işlət
+
+
+ Silmə prosesini təsdiqləyirsiniz?
+ %s faylı silmək istdəyinizə əminsiniz?
+ %s\'i zibil qutusuna atmaq istədiyinizə əminsiniz?
+ Faylı silmək istədiyinizə əminsiniz?
+ Bu faylı zibil qutusuna atmaq istədiyinizə əminsiniz?
+ Bu oturumda birdə soruşma
+ Bəli
+ Xeyr
+
+
+ - XƏBƏRDARLIQ: Qovluğu silirsiniz
+ - XƏBƏRDARLIQ: %d sayda qovluğu silirsiniz
+
+
+
+ PIN
+ PIN\'i daxil edin
+ Xahiş olunur PIN\'i daxil edin
+ PIN yanlışdır
+ PIN\'i təkrarlayın
+ Model
+ Model daxil edin
+ Yanlış model
+ Modeli təkrarlayın
+ Barmaq izi
+ Barmaq izi əlavə et
+ Xahiş olunur barmağınızı barmaq izi sensoru üzərinə yerləşdirin
+ Uyğunlaşdırma uğursuzdur
+ Uyğunlaşdırma bloklanıb, biraz sonra yoxlayın
+ Qeydiyyata alınmış heçbir barmaq izi yoxdur, xahiş olunur cihazınızın Parametrlərindən əlavə edəsiniz
+ Parametrlərə get
+ Şifrə təyini uğurludur. Əgər şifrəni unutsanız, tətbiqi yenidən yükləyin.
+ Qoruma təyini uğurludur. Əgər təzələmə problemi yaşasanız, tətbiqi yenidən yükləyin.
+
+
+ Dünən
+ Bugün
+ Sabah
+ saniyələr
+ dəqiqələr
+ saatlar
+ günlər
+
+
+ - %d saniyə
+ - %d saniyə
+
+
+ - %d dəqiqə
+ - %d dəqiqə
+
+
+ - %d saat
+ - %d saat
+
+
+ - %d gün
+ - %d gün
+
+
+ - %d həftə
+ - %d həftə
+
+
+ - %d ay
+ - %d ay
+
+
+ - %d il
+ - %d il
+
+
+
+
+ - %d saniyə
+ - %d saniyə
+
+
+ - %d dəqiqə
+ - %d dəqiqə
+
+
+ - %d saat
+ - %d saat
+
+
+
+
+ - %d saniyə əvvəl
+ - %d saniyə əvvəl
+
+
+ - %d dəqiqə əvvəl
+ - %d dəqiqə əvvəl
+
+
+ - %d saat əvvəl
+ - %d saat əvvəl
+
+
+ - %d gün əvvəl
+ - %d gün əvvəl
+
+
+
+
+ - %d saniyə
+ - %d saniyə
+
+
+ - %d dəqiqə
+ - %d dəqiqə
+
+
+ - %d saat
+ - %d saat
+
+
+
+ Zəng dayanana qədər qalan vaxt:\n%s
+ Xatırladıcının işləməsinə qalan vaxt:\n%s
+ Xahiş olunur zəngə istinad etməzdən əvvəl işləyib işləmədiyini yoxlayın. Güc qoruma parametrləri onun işləməsində problem yarada bilər.
+ Xahiş olunur xatırladıcılara istinad etməzdən əvvəl işləyib işləmədiyini yoxlayın. Güc qoruma parametrləri onların işləməsində problem yarada bilər.
+
+
+ Zəng
+ Yuxu
+ Burax
+ Xatırladıcı yoxdur
+ Başlanğıcda
+ Sistem səsləri
+ Sizin səsləriniz
+ Yeni səs əlavə et
+ Səs yoxdur
+
+
+ Parametrlər
+ Sadə Təşəkkür\'ü Alın
+ Rəngləri dəyiş
+ Widget rənglərini dəyiş
+ İngilis dilində işlət
+ Başlanğıcda yeni nə var göstərmə
+ Gizli faylları göstər
+ Yazı ölçüsü
+ Kiçik
+ Orta
+ Böyük
+ Çox böyük
+ Şifrə gizlənmiş tətbiqin görünürlüyünü qoruyur
+ Şifrə bütün tətbiqi qoruyur
+ Faylı kopyalama/daşıma/yenidən adlandırma proseslərində son faylı saxla
+ Sürüşdürmə çubuğu ilə hərəkət zamanı məlumat ekranını göstər
+ Tətbiq aktiv halda olduqda telefonun yuxuya getməsinin qarşısını al
+ Silmə təsdiq dialoqunu göstərmə
+ Yuxarıdan çək-təzələ\'ni aç
+ 24 saat formatından istifadə et
+ Həftəyə Bazar Ertəsindən başla
+ Widgetlər
+ Həmişə eyni yuxu intervalından istifadə et
+ Yuxu intervalı
+ Düyməyə toxunduqda titrə
+ Faylları silməkdənsə Zibil qutusuna at
+ Zibil qutusu təmizlik intervalı
+ Zibil qutusunu boşalt
+ Portret moduna zorla
+
+
+ Görünürlük
+ Qoruma
+ Sürüşdürmə
+ Fayl əməliyyatları
+ Zibil Qutusu
+ Saxlama
+ Başlanğıc
+ Mətn
+
+
+ Bu faylı geri qaytar
+ Bu faylları geri qaytar
+ Bütün faylları geri qaytar
+ Zibil qutusu uğurla boşaldıldı
+ Fayllar uğurla geri qaytarıldı
+ Zibil qutusunu boşaltmaq istədiyinizə əminsiniz? Fayllar tamamilə itiriləcək.
+ Zibil qutusu boşdur
+
+
+ Daxil edilir…
+ Çıxarılır…
+ Daxiletmə uğurludur
+ Çıxarış uğurludur
+ Daxiletmə uğursuzdur
+ Çıxarış uğursuzdur
+ Bəzi girişləri daxil etmək olmur
+ Bəzi girişləri çıxarmaq olmur
+ Çıxarış üçün heçbir giriş tapılmadı
+
+
+ OTG
+ Xahiş edirik icazə vermək üçün sonrakı ekranda çıxan OTG cihazın root qovluğunu seçin
+ Yanlış qovluq seçilib, xahiş olunur OTG cihazın root qovluğunu seçin
+
+
+ Yanvar
+ Fevral
+ Mart
+ Aprel
+ May
+ İyun
+ İyul
+ Avqust
+ Sentyabr
+ Oktyabr
+ Noyabr
+ Dekabr
+
+
+ Yanvarda
+ Fevralda
+ Martda
+ Apreldə
+ Mayda
+ İyunda
+ İyulda
+ Avqustda
+ Sentyabrda
+ Oktyabrda
+ Noyabrda
+ Dekabrda
+
+ Bazar Ertəsi
+ Çərşənbə Axşamı
+ Çərşənbə
+ Cümə Axşamı
+ Cümə
+ Şənbə
+ Bazar
+
+ B.E
+ Ç.A
+ Ç
+ C.A
+ C
+ Ş
+ B
+
+ Bzr.E
+ Çrş.A
+ Çrş
+ Cü.A
+ Cü
+ Şnb
+ Bzr
+
+
+ Haqqında
+ Kök koda keçiş üçün
+ Geri dönüş və tövsiyələrinizi göndərin
+ Daha çox tətbiq
+ Üçüncü partiya lisenziyaları
+ Dostları dəvət et
+ Ey! gəl sən də %2$s\'də %1$s\'ə nəzər yetir!
+ Dəvət et
+ Dəyərləndir
+ İanə et
+ İanə et
+ İzlə
+ v %1$s\nMüəllif Hüquqları © Simple Mobile Tools %2$d
+ Əlavə məlumat
+ Tətbiq versiyası: %s
+ Cihaz ƏS: %s
+
+
+ arzu edirəm tətbiqdən zövq alırsan. Reklam ehtiva etmir, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Ödəniş Et
+ Xahiş olunur Sadə Təşəkkür tətbiqini ən son versiyaya yeniləyin
+ Sual verməmişdən qabaq, Tez-tez soruşulan sualları oxuyun, ola bilər cavab oradadır.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Tez-tez Soruşulan Suallar
+ Xahiş olunur sual verməmişdən qabaq aşağıdakı bölməni oxuyun
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ Bu tətbiq həyatımı asanlaşdırmaq üçün aşağıdakı üçüncü partiya tətbiq kitabxanalarından istifadə edir. Təşəkkürlər.
+ Üçüncü partiya lisenziyaları
+ Kotlin (proqramlaşdırma dili)
+ Subsampling Scale Image View (yaxınlaşdırılan şəkillər)
+ Glide (şəkil açma və keşləmə)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-br/strings.xml b/commons/src/main/res/values-br/strings.xml
new file mode 100644
index 0000000..f220aee
--- /dev/null
+++ b/commons/src/main/res/values-br/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Cancel
+ Save as
+ File saved successfully
+ Invalid file format
+ Out of memory error
+ An error occurred: %s
+ Open with
+ No valid app found
+ Set as
+ Value copied to clipboard
+ Unknown
+ Always
+ Norse
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Favorites
+ Add favorites
+ Add to favorites
+ Remove from favorites
+
+
+ Search
+ Type in at least 2 characters to start the search.
+
+
+ Filter
+ No items found.
+ Change filter
+
+
+ Storage permission is required
+ Contacts permission is required
+ Camera permission is required
+ Audio permission is required
+
+
+ Rename file
+ Rename folder
+ Could not rename the file
+ Could not rename the folder
+ Folder name must not be empty
+ A folder with that name already exists
+ Cannot rename the root folder of a storage
+ Folder renamed successfully
+ Renaming folder
+ Filename cannot be empty
+ Filename contains invalid characters
+ Filename \'%s\' contains invalid characters
+ Extension cannot be empty
+ Source file %s doesn\'t exist
+
+
+ Copy
+ Move
+ Copy / Move
+ Copy to
+ Move to
+ Source
+ Destination
+ Select destination
+ Click here to select destination
+ Could not write to the selected destination
+ Please select a destination
+ Source and destination cannot be the same
+ Could not copy the files
+ Copying…
+ Files copied successfully
+ An error occurred
+ Moving…
+ Files moved successfully
+ Some files could not be moved
+ Some files could not be copied
+ No files selected
+ Saving…
+ Could not create folder %s
+ Could not create file %s
+
+
+ Create new
+ Folder
+ File
+ Create new folder
+ A file or folder with that name already exists
+ The name contains invalid characters
+ Please enter a name
+ An unknown error occurred
+
+
+ File \"%s\" already exists
+ File \"%s\" already exists. Overwrite?
+ Folder \"%s\" already exists
+ Merge
+ Overwrite
+ Skip
+ Append with \'_1\'
+ Apply to all
+
+
+ Select a folder
+ Select a file
+ Confirm external storage access
+ Please choose the root folder of the SD card on the next screen, to grant write access
+ If you don\'t see the SD card, try this
+ Confirm selection
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Select storage
+ Internal
+ SD Card
+ Root
+ Wrong folder selected, please select the root folder of your SD card
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Properties
+ Path
+ Items selected
+ Direct children count
+ Total files count
+ Resolution
+ Duration
+ Artist
+ Album
+ Focal length
+ Exposure time
+ ISO speed
+ F-number
+ Camera
+ EXIF
+ Song title
+
+
+ Background color
+ Text color
+ Primary color
+ Foreground color
+ App icon color
+ Restore defaults
+ Change color
+ Theme
+ Changing a color will make it switch to Custom theme
+ Save
+ Discard
+ Undo changes
+ Are you sure you want to undo your changes?
+ You have unsaved changes. Save before exit?
+ Apply colors to all Simple Apps
+ Colors updated successfully. A new Theme called \'Shared\' has been added, please use that for updating all app colors in the future.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Light
+ Dark
+ Solarized
+ Dark red
+ Black & White
+ Personelaet
+ Shared
+
+
+ What\'s new
+ * only the bigger updates are listed here, there are always some smaller improvements too
+
+
+ Delete
+ Remove
+ Rename
+ Share
+ Share via
+ Select all
+ Hide
+ Unhide
+ Hide folder
+ Unhide folder
+ Temporarily show hidden
+ Stop showing hidden media
+ You cannot share this much content at once
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sort by
+ Name
+ Size
+ Last modified
+ Date taken
+ Title
+ Filename
+ Extension
+ Ascending
+ Descending
+ Use for this folder only
+
+
+ Are you sure you want to proceed with the deletion?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Do not ask again in this session
+ Yes
+ No
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Enter PIN
+ Please enter a PIN
+ Wrong PIN
+ Repeat PIN
+ Pattern
+ Insert pattern
+ Wrong pattern
+ Repeat pattern
+ Fingerprint
+ Add fingerprint
+ Please place your finger on the fingerprint sensor
+ Authentication failed
+ Authentication blocked, please try again in a moment
+ You have no fingerprints registered, please add some in the Settings of your device
+ Go to Settings
+ Password setup successfully. Please reinstall the app in case you forget it.
+ Protection setup successfully. Please reinstall the app in case of problems with reseting it.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ a vunutennoù
+ eur
+ a zevezhioù
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+ - %ddevezh
+ - %da zevezhioù
+
+
+ - %dsizhun
+ - %dsizhun
+
+
+ - %dmiz
+ - %da vizioù
+
+
+ - %da vloavezhioù
+ - %da vloavezhioù
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %dvunutenn a-raok
+ - %da vunutennoù a-raok
+
+
+ - %deur a-raok
+ - %deur a-raok
+
+
+ - %da zevezhioù a-raok
+ - %da zevezhioù a-raok
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %dvunutenn
+ - %da vunutennoù
+
+
+ - %deur
+ - %da eurioù
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Daleañ
+ Dismiss
+ Adc\'halv ebet
+ D\'an deroù
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Settings
+ Purchase Simple Thank You
+ Customize colors
+ Customize widget colors
+ Use English language
+ Avoid showing What\'s New on startup
+ Show hidden items
+ Font size
+ Small
+ Medium
+ Large
+ Extra large
+ Password protect hidden item visibility
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ File operations
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importing…
+ Exporting…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ Genver
+ C\'hwevrer
+ Meurzh
+ Ebrel
+ Mae
+ Mezheven
+ Gouere
+ Eost
+ Gwengolo
+ Here
+ Du
+ Kerzu
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Lun
+ Meurzh
+ Merc\'her
+ Yaou
+ Gwener
+ Sadorn
+ Sul
+
+ L
+ M
+ M
+ Y
+ G
+ S
+ S
+
+ Lun
+ Meu
+ Mer
+ Yao
+ Gwe
+ Sad
+ Sul
+
+
+ About
+ For the source codes visit
+ Send your feedback or suggestions to
+ More apps
+ Third party licences
+ Invite friends
+ Hey, come check out %1$s at %2$s
+ Invite via
+ Rate us
+ Donate
+ Donate
+ Follow us
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Additional info
+ App version: %s
+ Device OS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ This app uses the following third party libraries to make my life easier. Thank you.
+ Third party licences
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-ca/strings.xml b/commons/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000..820e5e1
--- /dev/null
+++ b/commons/src/main/res/values-ca/strings.xml
@@ -0,0 +1,563 @@
+
+ OK
+ Cancel·lar
+ Desar com
+ Arxiu desat amb éxit
+ Format d\'arxiu no vàlid
+ Error: sense memòria
+ Ha ocorregut un error: %s
+ Obrir amb
+ No s\'ha trobat una aplicació vàlida
+ Establir com
+ Valor copiat al portaretalls
+ Desconegut
+ Sempre
+ Mai
+ Detalls
+ Notes
+ S\'està eliminant la carpeta \'%s\'
+ Cap
+ Label
+
+
+ Preferits
+ Afegeix als preferits
+ Afegeix als preferits
+ Elimina dels preferits
+
+
+ Buscar
+ Escriu al menys dos caracters per iniciar la cerca.
+
+
+ Filtrar
+ No s\'ha trobat arxius.
+ Canviar filtres
+
+
+ Es requereix permís d\'accés a l\'emmagatzematge
+ Es requereix permís d\'accés als contactes
+ Es requereix permís d\'accés a la càmera
+ Es requereix permís d\'accés a l\'audio
+
+
+ Canviar el nom de l\'arxiu
+ Canviar el nom de la carpeta
+ No s\'ha pogut canviar el nom de l\'arxiu
+ No s\'ha pogut canviar el nom de la carpeta
+ El no de la carpeta no pot ser buit
+ Ja existeix una carpeta amb aquest nom
+ No es pot canviar el nom de la carpeta arrel de l\'emmagatzematge
+ Carpeta canviada de nom correctament
+ Canviant el nom de la carpeta
+ El nom de l\'arxiu no pot ser buit
+ El nom de l\'arxiu conté caràcters no vàlids
+ El nom de l\'arxiu \'%s\' conté caràcters no vàlids
+ La extensió no pot estar buida
+ El fitxer %s no existeix
+
+
+ Copiar
+ Moure
+ Copiar/Moure
+ Copiar a
+ Moure a
+ Origen
+ Destí
+ Seleccionar destí
+ Prem aquí per triar el destí
+ No es pot escriure al destí triat
+ Si us plau tria un destí
+ Origen i destí no poden ser iguals
+ No s\'ha pogut copiar els arxius
+ Copiant…
+ Arxius copiats correctament
+ Ha ocorregut un error
+ Movent…
+ Arxius moguts correctament
+ Alguns arxius no s\'han pogut moure
+ Alguns arxius no s\'han pogut copiar
+ No hi ha elements seleccionats
+ Desant…
+ No es pot crear la carpeta %s
+ No es pot crear l\'arxiu %s
+
+
+ Crear nova
+ Carpeta
+ Arxiu
+ Crear nova carpeta
+ Ja existeix un arxiu o carpeta amb aquest nom
+ El nom conté caràcters no vàlids
+ Si us plau introdueix un nom
+ Ha ocorregut un error desconegut
+
+
+ L’arxiu \"%s\" ja existeix
+ L’arxiu \"%s\" ja existeix. El vos sobreescriure?
+ La carpeta \"%s\" ja existeix
+ Fusionar
+ Sobreescriure
+ Saltar
+ Afegir \'_1\'
+ Aplicar a tot
+
+
+ Selecciona una carpeta
+ Selecciona un arxiu
+ Permet l’accés a l’emmagatzematge extern
+ Si us plau, tria la carpeta arrel de la targeta SD a la propera pantalla per concedir accés d’escriptura
+ Si no veus la targeta SD, prova això
+ Confirmar Selecció
+
+
+ - %d element
+ - %d elements
+
+
+
+
+ - %d element
+ - %d elements
+
+
+
+ - Eliminant %d element
+ - Eliminant %d elements
+
+
+
+ Selecciona emmagatzematge
+ Emmagatzematge intern
+ Targeta SD
+ Arrel /
+ Has seleccionat la carpeta incorrecta. Selecciona la carpeta arrel de la targeta SD
+ La targeta SD i el dispositiu OTG no poden ser el mateix
+ Sembla que l\'aplicació està instal·lada a una targeta SD, cosa que fa que els widgets de l\'aplicació no estiguin disponibles. No els veuràs ni a la llista de widgets disponibles.
+ És una limitació del sistema, de manera que si voleu utilitzar els widgets, heu de tornar a moure l\'aplicació a l\'emmagatzematge intern.
+
+
+ Propietats
+ Ubicació
+ Elements seleccionats
+ Número d’arxius directament continguts
+ Número total d’arxius
+ Resolució
+ Duració
+ Artista
+ Àlbum
+ Longitut focal
+ Temps d’exposició
+ Velocitat ISO
+ F-number
+ Càmera
+ EXIF
+ Títol de la canço
+
+
+ Color del fons
+ Color del text
+ Color principal
+ Color de primer pla
+ Color de la icona d\'aplicació
+ Valors per defecte
+ Canviar de color
+ Tema
+ Canviar un color farà que canvii a tema personalitzat
+ Dear
+ Desfer
+ Desfer els canvis
+ ¿Segur que vols desfer els canvis?
+ Tens canvis sense aplicar. Desar abans de sortir?
+ Aplicar els colors a totes les aplicacions Simple Apps
+ Colors actualitzats satisfactoriament. S’ha afegit un nou tema anomenat \'Shared\'. Fes -lo servidr per actualitzar els colors de les aplicacions en el futur.
+
+ Simple Thank You per desblocar aquesta fucnió i ajudar al desenvolupament. Gràcies!
+ ]]>
+
+
+
+ Clar
+ Fosc
+ Solaritzat
+ Vermell fosc
+ Blanc & Negre
+ Personalizat
+ Compartit
+
+
+ Que hi ha nou
+ * Només les actualitzacions més grans s’enumeren aquí. Sempre hi ha petites millores també.
+
+
+ Eliminar
+ Canviar el nom
+ Compartir
+ Compartir amb
+ Seleccionar tot
+ Ocultar
+ Mostrar
+ Amagar carpeta
+ Mostrar carpeta
+ Mostrar amagats temporalment
+ Deixar de mostrar mitjans amagats
+ No pots compartir aquest contingut alhora
+ Buida i desactiva la paperera de reciclatge
+ Desfer
+ Refer
+
+
+ Ordenar per
+ Nom
+ Mida
+ Modificació
+ Creació
+ Títol
+ Nom de l’arxiu
+ Extensió
+ Ascendent
+ Descendent
+ Només per aquesta carpeta
+
+
+ Estas segur que vols continuar amb l’eliminació?
+ Esteu segur que voleu suprimir %s?
+ Estàs segur que vols moure %s a la paperera?
+ Segur que voleu eliminar aquest element?
+ Segur que voleu moure aquest element a la paperera?
+ No tornar a preguntar en aquesta sessió
+ Si
+ No
+
+
+ - ADVERTIMENT: s\'eliminarà una carpeta
+ - ADVERTIMENT: s\'eliminaran %d carpetes
+
+
+
+ PIN
+ Introdueix PIN
+ Si us plau introdueix PIN
+ PIN erroni
+ Repeteix PIN
+ Patró
+ Introdueix patró
+ Patró erroni
+ Repeteix patró
+ Empremta digital
+ Afegeix Empremta digital
+ Si us plau posa el teu dit al sensor d’empremta digital
+ L\'autenticació ha fallat
+ L\'autenticació està bloquejada. Prova-ho d’aquí un moment
+ No tens empremtes digitals registrades. Afegeix-ne alguna a la configuració del dispositiu
+ Anar als ajustaments
+ Configuració de la contrasenya amb èxit. Torneu a instal·lar l\'aplicació en cas que l\'oblideu.
+ Configuració de la protecció amb èxit. Torneu a instal·lar l\'aplicació en cas de problemes amb restablir-la.
+
+
+ Ahir
+ Avui
+ Demà
+ segons
+ minuts
+ hores
+ dies
+
+
+ - %d segon
+ - %d segons
+
+
+ - %d minut
+ - %d minuts
+
+
+ - %d hora
+ - %d hores
+
+
+ - %d dia
+ - %d dies
+
+
+ - %d setmana
+ - %d setmanes
+
+
+ - %d mes
+ - %d mesos
+
+
+ - %d any
+ - %d anys
+
+
+
+
+ - %d segon
+ - %d segons
+
+
+ - %d minut
+ - %d minuts
+
+
+ - %d hora
+ - %d hores
+
+
+
+
+ - %d segon abans
+ - %d segons abans
+
+
+ - %d minut abans
+ - %d minuts abans
+
+
+ - %d hora abans
+ - %d hores abans
+
+
+ - %d dia abans
+ - %d dies abans
+
+
+
+
+ - %d segon
+ - %d segons
+ - %d segons
+
+
+ - %d minut
+ - %d minuts
+ - %d minuts
+
+
+ - %d hora
+ - %d hores
+ - %d hores
+
+
+
+ Temps restant fins que soni l\'alarma: \n%s
+ Temps restant fins el recordatori:\n%s
+ Assegureu-vos que l\'alarma funcioni correctament abans de confiar-hi. Pot comportar-se malament a causa de les restriccions del sistema relacionades amb l\'estalvi de bateria.
+ Assegureu-vos que els recordatoris funcionin correctament abans de confiar-hi. Podrien comportar-se malament a causa de les restriccions del sistema relacionades amb l\'estalvi de bateria.
+
+
+ Alarma
+ Posposar
+ Cancel·lar
+ Cap recordatori
+ En començar
+ Sons del sistema
+ Els teus sons
+ Afegeix un so nou
+ Sense so
+
+
+ Ajustaments
+ Compra Simple Thank You
+ Canviar colors
+ Personalitza els colors de widgets
+ Usar l’idioma Anglès
+ Permetre mostrar novetats a l\'inici
+ Mostra elements ocults
+ Mida de la font
+ Petita
+ Mitjana
+ Gran
+ Enorme
+ Contrasenya per protegir els mitjans amagats
+ Contrasenya per protegir tota l’aplicació
+ Mantenir el valor de la darrera modificació al copiar/moure/canviar el nom dels arxius
+ Mostra una bombolla d\'informació en elements de desplaçament mitjançant arrossegament de la barra de desplaçament
+ No deixar dormir el telèfon amb la aplicació a primer pla
+ Ignora sempre el diàleg de confirmació d\'eliminació
+ Activa estirar des de la part superior per refrescar
+ Utilitzar foramt 24 houres
+ Començar la setmana en diumenge
+ Ginys
+ Utilitzeu sempre el mateix temps de posposat
+ Temps de posposat
+ Vibrar al prŕmer un botó
+ Mou elements a la paperera de reciclatge en lloc de suprimir-los
+ Interval de neteja de paperera de reciclatge
+ Buida la paperera de reciclatge
+ Força el mode de retrat
+
+
+ Visibilitat
+ Seguretat
+ Desplaçament
+ Operacions de fitxers
+ Paperera de reciclatge
+ Desant
+ Inici
+ Text
+
+
+ Restaura aquest fitxer
+ Restaura els fitxers seleccionats
+ Restaura tots els fitxers
+ La paperea s\'ha buidat correctament
+ El fitxers s\'han restaurat correctament
+ ASegur que vols buidar la paperera? El fitxer s\'eliminaran definitivament.
+ La paperera és buida
+
+
+ Important…
+ Exportant…
+ Importació correcta
+ Exportació correcta
+ Importacó fallida
+ Exportació fallida
+ L\'impotació d\'algunes entrades ha fallat
+ L\'exportació d\'algunes entrades ha fallat
+ No s\'han trobat entrades per exportar
+
+
+ OTG
+ Seleccioneu la carpeta arrel del dispositiu OTG a la pantalla següent per concedir accés
+ S\'ha seleccionat la carpeta incorrecta, seleccioneu la carpeta arrel del vostre dispositiu OTG
+
+
+ Gener
+ Febrer
+ Març
+ Abril
+ Maig
+ Juny
+ Juliol
+ Agost
+ Setembre
+ Octubre
+ Novembre
+ Decembre
+
+
+ al Gener
+ al Febrer
+ al Març
+ a l\'Abril
+ al Maig
+ al Juny
+ al Juliol
+ a l\'Agost
+ al Setembre
+ a l\'Octubre
+ al Novembre
+ al Decembre
+
+ Dilluns
+ Dimarts
+ Dimecres
+ Dijous
+ Divendres
+ Dissabte
+ Diumenge
+
+ DL
+ DT
+ DC
+ DJ
+ DV
+ DS
+ DG
+
+ DL
+ DT
+ DC
+ DJ
+ DV
+ DS
+ DG
+
+
+ Sobre
+ Més aplicacions senzilles i codi font a
+ Envia els teus comentaris o suggeriments a
+ Més aplicacions
+ Llicències de tercers
+ Convida a amics
+ Hola, vine i mira %1$s a %2$s
+ Convida amb
+ vota\'ns a Play Store
+ Donar
+ Donar
+ Segueix-nos
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Informació adicional
+ Versió de la APP: %s
+ Sistema operatiu: %s
+
+
+ Esperem que gaudiu de l\'aplicació. No conté cap anunci, així que si us plau, ajudeu el seu desenvolupament comprant la aplicació Simple Thank You , també evitarà que aquest diàleg es torni a mostrar.
+ Gràcies!
+ ]]>
+
+ Comprar
+ Si us plau, actualitzeu Simple Thank You a la darrera versió
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ només informar-te que una nova aplicació s\'ha publicat recentment:
+ %2$s
+ Pots descarregar-la prement el títol.
+ Gràcies
+ ]]>
+
+
+
+ Preguntes frequents
+ Abans de fer una pregunta, primer llegeix les
+ Com és que no veig aquest widget a la llista de widgets?
+ Probablement, perquè heu mogut l\'aplicació en una targeta SD. Hi ha una limitació del sistema d\'Android que oculta els widgets de l\'aplicació determinada
+ en aquest cas. L\'única solució és moure l\'aplicació a l\'emmagatzematge intern a través de la configuració del dispositiu.
+ Vull donar-vos suport, però no puc donar diners. Hi ha alguna cosa més que pugui fer?
+ Sí, per suposat. Podeu donar a conneixer les aplicacions o donar-ne una bona opinió i valoracions. També podeu ajudar per traduir les aplicacions en un nou idioma o, simplement, actualitzar algunes de les traduccions existents.
+ Podeu trobar la guia a https://github.com/SimpleMobileTools/General-Discussion, o simplement enviar-me un missatge a hello@simplemobiletools.com si necessiteu ajuda.
+ He esborrat alguns fitxers per error, com puc recuperar-los?
+ Malauradament, no pots fer-ho. Els fitxers s\'eliminen a l\'instant després del diàleg de confirmació, no hi ha una paperera disponible.
+ No m\'agraden els colors del widget, puc canviar-los?
+ Sí, mentre arrossegueu un widget a la vostra pantalla d\'inici, apareixerà una pantalla de configuració de widgets. Veureu quadrats de colors a l\'extrem inferior esquerre, només cal prémer-los per seleccionar un color nou. També podeu utilitzar el control lliscant per ajustar l\'alfa.
+ Puc recuperar d\'alguna manera els fitxers eliminats?
+ Si estan realment eliminats no pots fer-ho. Tanmateix, pots habilitar l\'ús d\'una paperera de reciclatge en comptes de suprimir definitivament a la configuració de l\'aplicació. Això només mourà els fitxers en lloc d\'eliminar-los.
+
+
+ Aquesta aplicació fa servir les següents biblioteques de tercers que ens faciliten la feina. Gràcies.
+ Llicències de tercers
+ Kotlin (Programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Selector de números (selector de números personalitzable)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-cs/strings.xml b/commons/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..e433724
--- /dev/null
+++ b/commons/src/main/res/values-cs/strings.xml
@@ -0,0 +1,578 @@
+
+ OK
+ Zrušit
+ Uložit jako
+ Soubor úspěšně uložen
+ Neplatný formát souboru
+ Zařízení nemá dostatek paměti
+ Vyskytla se chyba: %s
+ Otevřít pomocí
+ Nebyla nalezena žádná vhodná aplikace
+ Nastavit jako
+ Value copied to clipboard
+ Unknown
+ Always
+ Never
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Oblíbené
+ Add favorites
+ Přidat do oblíbených
+ Odebrat z oblíbených
+
+
+ Hledat
+ Type in at least 2 characters to start the search.
+
+
+ Filtrovat
+ No items found.
+ Change filter
+
+
+ Storage permission is required
+ Contacts permission is required
+ Camera permission is required
+ Audio permission is required
+
+
+ Přejmenovat soubor
+ Přejmenovat složku
+ Nelze přejmenovat soubor
+ Nelze přejmenovat složku
+ Název složky nesmí být prázdný
+ Složka s tímto názvem již existuje
+ Nelze přejmenovat root složku úložiště
+ Složka byla úspěšně přejmenována
+ Přejmenování složky
+ Název souboru nemůže být prázdný
+ Název souboru obsahuje neplatné znaky
+ Název souboru \'%s\' obsahuje neplatné znaky
+ Přípona nemůže být prázdná
+ Source file %s doesn\'t exist
+
+
+ Kopírovat
+ Přesunout
+ Kopírovat / Přesunout
+ Kopírovat do
+ Přesunout do
+ Zdroj
+ Cíl
+ Zvolit cílovou složku
+ Klikněte zde pro volbu cílové složky
+ Nelze zapisovat do zvolené destinace
+ Prosím zvolte cílovou složku
+ Zdrojová a cílová složka se musí lišit
+ Nelze zkopírovat soubory
+ Kopíruji…
+ Soubory byly úspěšně zkopírovány
+ Vyskytla se chyba
+ Přesunuji…
+ Soubory byly úspěšně přesunuty
+ Některé soubory nemohly být přesunuty
+ Některé soubory nemohly být zkopírovány
+ Žádné soubory vybrány
+ Ukládám…
+ Could not create folder %s
+ Could not create file %s
+
+
+ Nový
+ Složka
+ Soubor
+ Vytvořit novou složku
+ Soubor nebo složka s tímto názvem již existuje
+ Název obsahuje neplatné znaky
+ Zadejte prosím název
+ Vyskytla se neznámá chyba
+
+
+ Soubor \"%s\" již existuje
+ Soubor \"%s\" již existuje. Přepsat?
+ Složka \"%s\" již existuje
+ Merge
+ Přepsat
+ Přeskočit
+ Ponechat s \'_1\'
+ Použít pro všechny
+
+
+ Zvolte složku
+ Zvolte soubor
+ Potvrďte přístup k externímu úložišti
+ Na následující obrazovce prosím zvolte root složku SD karty pro povolení oprávnění k zápisu
+ Pokud SD kartu nevidíte, vyzkoušejte následující
+ Confirm selection
+
+
+ - %d položka
+ - %d položek
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Zvolit úložiště
+ Interní
+ SD Karta
+ Root
+ Neplatná složka zvolena, zvolte prosím root složku vaší SD karty
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Vlastnosti
+ Cesta
+ Položek zvoleno
+ Počet přímých dětí
+ Celkový počet souborů
+ Rozlišení
+ Délka
+ Autor
+ Album
+ Ohnisková vzdálenost
+ Čas explozice
+ Citlivost ISO
+ F-číslo
+ Fotoaparát
+ EXIF
+ Song title
+
+
+ Barva pozadí
+ Barva písma
+ Primární barva
+ Foreground color
+ App icon color
+ Obnovit výchozí
+ Change color
+ Motiv
+ Změna barvy změní motiv na Vlastní
+ Uložit
+ Zahodit
+ Vrátit změny zpět
+ Opravdu chcete vrátit změny zpět?
+ Máte neuložené změny. Uložit před zavřením?
+ Apply colors to all Simple Apps
+ Colors updated successfully. A new Theme called \'Shared\' has been added, please use that for updating all app colors in the future.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Světlý
+ Tmavý
+ Solarizovaná
+ Dark red
+ Black & White
+ Vlastní
+ Shared
+
+
+ Co je nového
+ * pouze větší změny jsou zde uvedeny, vždy jsou doprovázeny také menšími změnami
+
+
+ Smazat
+ Remove
+ Přejmenovat
+ Sdílet
+ Sdílet pomocí
+ Označit vše
+ Skrýt
+ Odkrýt
+ Skrýt složku
+ Odkrýt složku
+ Dočasně zobrazit skryté
+ Stop showing hidden media
+ You cannot share this much content at once
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Seřadit dle
+ Názvu
+ Velikosti
+ Poslední úpravy
+ Data pořízení
+ Titulku
+ Názvu souboru
+ Přípony
+ Vzestupně
+ Sestupně
+ Použít pouze pro tuto složku
+
+
+ Opravdu chcete smazat dané soubory/složky?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Do not ask again in this session
+ Ano
+ Ne
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Enter PIN
+ Please enter a PIN
+ Wrong PIN
+ Repeat PIN
+ Pattern
+ Insert pattern
+ Wrong pattern
+ Repeat pattern
+ Fingerprint
+ Add fingerprint
+ Please place your finger on the fingerprint sensor
+ Authentication failed
+ Authentication blocked, please try again in a moment
+ You have no fingerprints registered, please add some in the Settings of your device
+ Go to Settings
+ Password setup successfully. Please reinstall the app in case you forget it.
+ Protection setup successfully. Please reinstall the app in case of problems with reseting it.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minuty
+ hodiny
+ dny
+
+
+ - %d second
+ - %d seconds
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+ - %d hours
+
+
+ - %d den
+ - %d dni
+ - %d dní
+
+
+ - %d týden
+ - %d týdni
+ - %d týdnů
+
+
+ - %d měsíc
+ - %d měsíce
+ - %d měsíců
+
+
+ - %d rok
+ - %d roky
+ - %d roků
+
+
+
+
+ - %d second
+ - %d seconds
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+ - %d seconds before
+
+
+ - před %d minutou
+ - před %d minutami
+ - před %d minutami
+
+
+ - před %d hodinou
+ - před %d hodinami
+ - před %d hodinami
+
+
+ - před %d dnem
+ - před %d dnami
+ - před %d dny
+
+
+
+
+ - %d second
+ - %d seconds
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+ - %d hours
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ Žádné připomenutí
+ Na začátku
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Nastavení
+ Purchase Simple Thank You
+ Přizpůsobit barvy
+ Customize widget colors
+ Use English language
+ Avoid showing What\'s New on startup
+ Zobrazit skryté položky
+ Font size
+ Small
+ Medium
+ Large
+ Extra large
+ Password protect hidden item visibility
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ File operations
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importování…
+ Exportování…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ leden
+ únor
+ březen
+ duben
+ květen
+ červen
+ červenec
+ srpen
+ září
+ říjen
+ listopad
+ prosinec
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Pondělí
+ Úterý
+ Středa
+ Čtvrtek
+ Pátek
+ Sobota
+ Neděle
+
+ P
+ Ú
+ S
+ Č
+ P
+ S
+ N
+
+ Pon
+ Úte
+ Stř
+ Čtv
+ Pát
+ Sob
+ Ned
+
+
+ O aplikaci
+ Pro zdrojové kódy navštivte
+ Svoji zpětnou vazbu či návhry posílejte na
+ Více aplikací
+ Licence třetí strany
+ Pozvat přátele
+ Ahoj, pojď se podívat na %1$s na %2$s
+ Pozvat pomocí
+ Ohodnotit
+ Donate
+ Donate
+ Sledujte nás
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Additional info
+ App version: %s
+ Device OS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ Tato aplikace využívá následující knihovny třetích stran pro zjednodušení mého žívota. Děkuji.
+ Licence třetích stran
+ Kotlin (programovací jazyk)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-da/strings.xml b/commons/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..ad543ca
--- /dev/null
+++ b/commons/src/main/res/values-da/strings.xml
@@ -0,0 +1,560 @@
+
+ OK
+ Annuller
+ Gem som
+ Fil gemt
+ Ugyldigt filformat
+ Fejl, ikke mere plads
+ Der opstod en fejl: %s
+ Åbn med
+ Der blev ikke fundet en gyldig app
+ Anvend som
+ Værdi kopieret til udklipsholder
+ Ukendt
+ Altid
+ Aldrig
+ Detaljer
+ Noter
+ Sletter mappen \'%s\'
+ None
+ Label
+
+
+ Bogmærker
+ Tilføj bogmærker
+ Føj til bogmærker
+ Fjern fra bogmærker
+
+
+ Søg
+ Start søgningen ved at skrive mindst to tegn.
+
+
+ Filter
+ Intet fundet.
+ Skift filter
+
+
+ Tilladelse til at gemme påkrævet
+ Adgang til kontakter påkrævet
+ Adgang til kamera påkrævet
+ Adgang til lyd påkrævet
+
+
+ Omdøb fil
+ Omdøb mappe
+ Kunne ikke omdøbe filen
+ Kunne ikke omdøbe mappen
+ Mappen skal have et navn
+ Navnet bruges allerede af en anden mappe
+ Kan ikke omdøbe lagerets rodmappe
+ Mappen er omdøbt
+ Omdøber mappe
+ Filen skal have et navn
+ Filnavnet indeholder ugyldige tegn
+ Filnavnet \"%s\" indeholder ugyldige tegn
+ Filtypen kan ikke stå tom
+ Kildefilen %s eksisterer ikke
+
+
+ Kopier
+ Flyt
+ Kopier/Flyt
+ Kopier til
+ Flyt til
+ Kilde
+ Destination
+ Vælg destination
+ Klik her for at vælge destination
+ Kunne ikke skrive til den valgte destination
+ Vælg en destination
+ Til og fra skal være forskellige
+ Kunne ikke kopiere filerne
+ Kopierer…
+ Filerne er kopieret
+ Der opstod en fejl
+ Flytter…
+ Filerne er flyttet
+ Ikke alle filer blev flyttet
+ Nogle filer blev ikke kopieret
+ Der er ikke valgt nogen filer
+ Gemmer…
+ Kunne ikke oprette mappen %s
+ Kunne ikke oprette filen %s
+
+
+ Opret ny
+ Mappe
+ Fil
+ Opret en ny mappe
+ Navnet er allerede i brug
+ Navnet indeholder ugyldige tegn
+ Skriv et navn
+ Der opstod en ukendt fejl
+
+
+ Filen \"%s\" findes allerede
+ Filen \"%s\" findes allerede. Overskriv?
+ Mappen \"%s\" findes allerede
+ Sammenflet
+ Overskriv
+ Spring over
+ Tilføj \"_1\"
+ Anvend på alle konflikter
+
+
+ Vælg en mappe
+ Vælg en fil
+ Bekræft adgang til eksternt lager
+ Vælg SD-kortets rodmappe på næste side for at give skriveadgang til den.
+ Hvis ikke du kan se SD-kortet kan du prøve dette
+ Bekræft valg
+
+
+ - %d element
+ - %d elementer
+
+
+
+
+ - %d element
+ - %d elementer
+
+
+
+ - Sletter %d element
+ - Sletter %d elementer
+
+
+
+ Vælg lager
+ Intern
+ SD-kort
+ Rod
+ Forkert mappe valgt, vælg rodmappen på SD-kortet
+ Stierne til SD-kortet og OTG-enheden kan ikke være ens
+ Det ser ud til at du har appen installeret på et SD-kort. Det gør at appens widget ikke er tilgængelig og ikke kan ses på listen med widgets.
+ Dette skyldes en begrænsning i systemet, så hvis du vil bruge appens widgets må du flytte appen tilbage til det interne lager.
+
+
+ Egenskaber
+ Sti
+ Elementer valgt
+ Antal direkte underliggende
+ Antal filer i alt
+ Opløsning
+ Varighed
+ Forfatter
+ Album
+ Brændvidde
+ Eksponeringstid
+ ISO
+ F-nummer
+ Kamera
+ EXIF
+ Sangtitel
+
+
+ Baggrundsfarve
+ Tekstfarve
+ Primær farve
+ Forgrundsfarve
+ Farve til appens ikon
+ Gendan standarder
+ Skift farve
+ Udseende
+ Ændring af farve vil også medføre skift til tilpasset udseende
+ Gem
+ Kassér
+ Fortryd ændringer
+ Er du sikker på at du vil fortryde ændringerne?
+ Du har ændringer der ikke er gemt. Vil du gemme dem?
+ Anvend farverne på alle Simple Apps
+ Farverne er ændret. Et nyt udseende kaldet \"Tilpasset\" er tilføjet, brug det ved opdatering af alle app-farver fremover.
+
+ Simple Thank You for at låse denne funktion op og understøtte appens udvikling. Tak!
+ ]]>
+
+
+
+ Lys
+ Mørk
+ Solarized
+ Mørkerød
+ Sort & hvid
+ Tilpasset
+ Gemt
+
+
+ Hvad er nyt
+ * Kun større opdateringer er nævnt her, der er altid flere mindre forbedringer også
+
+
+ Slet
+ Fjern
+ Omdøb
+ Del
+ Del via
+ Vælg alle
+ Skjul
+ Vis
+ Skjul mappe
+ Vis mappe
+ Vis midlertidigt skjulte
+ Stop visning af skjulte medier
+ Du kan ikke dele så meget indhold ad gangen
+ Tøm og deaktiver papirkurven
+ Undo
+ Redo
+
+
+ Sorter efter
+ Navn
+ Størrelse
+ Senest ændret
+ Eksponeringsdato
+ Titel
+ Filnavn
+ Filtype
+ Stigende
+ Faldende
+ Anvend kun på denne mappe
+
+
+ Er du sikker på at du vil fortsætte med at slette?
+ Er du sikker på at du vil slette %s?
+ Er du sikker på at du vil flytte %s til papirkurven?
+ Er du sikker på at du vil slette dette?
+ Er du sikker på at du vil flytte dette til papirkurven?
+ Spørg ikke igen i denne session
+ Ja
+ Nej
+
+
+ - ADVARSEL: Du sletter en mappe
+ - ADVARSEL: Du sletter %d mapper
+
+
+
+ PIN
+ Indtast PIN
+ Indtast en PIN
+ Forkert PIN
+ Gentag PIN
+ Mønster
+ Indsæt mønster
+ Forkert mønster
+ Gentag mønster
+ Fingeraftryk
+ Tilføj fingeraftryk
+ Sæt din finger på sensoren
+ Godkendelsen fejlede
+ Godkendelse blokeret, prøv igen om lidt
+ Du har ingen registrerede fingeraftryk, tilføj nogle i indstillingerne på din enhed
+ Gå til indstillingerne
+ Adgangskoden er oprettet. Geninstaller appen hvis du glemmer den.
+ Beskyttelsen er oprettet. Geninstaller appen hvis du har problemer med at gendanne den.
+
+
+ I går
+ I dag
+ I morgen
+ sekunder
+ minutter
+ timer
+ dage
+
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %d minut
+ - %d minutter
+
+
+ - %d time
+ - %d timer
+
+
+ - %d dag
+ - %d dage
+
+
+ - %d uge
+ - %d uger
+
+
+ - %d måned
+ - %d måneder
+
+
+ - %d år
+ - %d år
+
+
+
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %d minut
+ - %d minutter
+
+
+ - %d time
+ - %d timer
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d minut før start
+ - %d minutter før start
+
+
+ - %d time før start
+ - %d timer før start
+
+
+ - %d dag før start
+ - %d dage før start
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minut
+ - %d minutter
+
+
+ - %d time
+ - %d timer
+
+
+
+ Resterende tid før alarmen lyder:\n%s
+ Tid tilbage inden alarmen lyder:\n%s
+ Du anbefales sikre dig at alarmen virker som den skal inden du stoler på den. Den kan drille som følge af strømbesparende begrænsninger på systemet.
+ Du anbefales sikre dig at påmindelser virker som de skal inden du stoler på dem. De kan drille som følge af strømbesparende begrænsninger på systemet.
+
+
+ Alarm
+ Udsæt
+ Slå fra
+ Ingen påmindelse
+ Ved start
+ Systemlyde
+ Dine lyde
+ Tilføj en ny lyd
+ Ingen lyd
+
+
+ Indstillinger
+ Køb Simple Thank You
+ Rediger farver
+ Rediger widget-farver
+ Use English language
+ Vis ikke seneste ændringer ved opstart
+ Vis skjulte elementer
+ Skriftstørrelse
+ Lille
+ Medium
+ Stor
+ Ekstra stor
+ Beskyt visning af skjulte elementer med en kode
+ Beskyt hele appen med en adgangskode
+ Kopi/flyt/omdøb skal ikke påvirke ændringstidspunktet
+ Vis en info-boble når jeg scroller med rullepanelet
+ Forhindr telefonen i at falde i søvn når appen er i forgrunden
+ Spørg ikke om bekræftelse før sletning
+ Aktiver træk nedad for at genindlæse
+ Brug 24-timersformat
+ Søndag første ugedag
+ Widgets
+ Brug altid samme udsættelsestid
+ Udsættelsestid
+ Vibrer ved tryk på knapper
+ Flyt elementer til papirkurven i stedet for at slette dem
+ Interval for tømning af papirkurven
+ Tøm papirkurven
+ Brug stående visning
+
+
+ Synlighed
+ Sikkerhed
+ Scrolling
+ Filoperationer
+ Papirkurv
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Er du sikker på at du vil tømme papirkurven? Filerne vil ikke kunne genskabes.
+ Papirkurven er tom
+
+
+ Importerer…
+ Eksporterer…
+ Import gennemført
+ Eksport gennemført
+ Import mislykkedes
+ Eksport mislykkedes
+ Import mislykkedes delvist
+ Eksport mislykkedes delvist
+ Der er ikke fundet indhold til eksport
+
+
+ OTG
+ Vælg rodmappen på OTG-enheden på den næste skærm for at give adgang.
+ Den forkerte mappe er valgt, vælg rodmappen på din OTG-enhed
+
+
+ Januar
+ Februar
+ Marts
+ April
+ Maj
+ Juni
+ Juli
+ August
+ September
+ Oktober
+ November
+ December
+
+
+ i januar
+ i februar
+ i marts
+ i april
+ i maj
+ i juni
+ i juli
+ i august
+ i september
+ i oktober
+ i november
+ i december
+
+ Mandag
+ Tirsdag
+ Onsdag
+ Torsdag
+ Fredag
+ Lørdag
+ Søndag
+
+ M
+ T
+ O
+ T
+ F
+ L
+ S
+
+ Man
+ Tir
+ Ons
+ Tor
+ Fre
+ Lør
+ Søn
+
+
+ Om
+ For at se kildekoden besøg
+ Send feedback og forslag til
+ Flere apps
+ 3.-parts licencer
+ Inviter venner
+ Hej, se %1$s på %2$s
+ Inviter via
+ Vurder os
+ Donér
+ Donér
+ Følg os
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Yderligere info
+ App version: %s
+ Device OS: %s
+
+
+ Håber du kan lide denne app. Den indeholder ingen reklamer, så vær sød at købe appen Simple Thank You , som også vil forhindre at denne besked vises igen.
+ På forhånd tak!
+ ]]>
+
+ Køb
+ Opdater venligst Simple Thank You til den seneste version
+ Inden du stiller et spørgsmål, bør du lige tjekke Ofte stillede spørgsmål, som måske allerede indeholder svaret.
+ Læs det
+
+
+
+
+ - synes lige du skulle vide at der er kommet en ny app:
+ %2$s
+ Du kan downloade den med et klik på titlen.
+ Tak
+ ]]>
+
+
+
+ Ofte stillede spørgsmål
+ Inden du stiller et spørgsmål bedes du først læse
+ Hvorfor kan jeg ikke se denne app\'s widget på listen med widgets?
+ Det er sikkert fordi du har flyttet appen til et SD-kort. Der er en begrænsning i Androidsystemet der i så fald skjuler appens widget. Eneste løsning er at flytte appen tilbage til det interne lager via indstillingerne på din enhed.
+ Jeg vil gerne støtte dig, men jeg kan ikke donere penge. Er der andet jeg kan gøre?
+ Ja, selvfølgelig. Du kan fortælle andre om appsene eller give god feedback og gode vurderinger. Du kan også hjælpe med at oversætte til et nyt sprog, eller med at opdatere en eksisterende oversættelse.
+ Du kan finde en guide på siden https://github.com/SimpleMobileTools/General-Discussion, eller send mig en besked på hello@simplemobiletools.com hvis du har brug for hjælp.
+ Jeg fik slettet nogle filer ved en fejl, hvordan kan jeg gendanne dem?
+ Det kan du desværre ikke. Filer er væk så snart sletningen er bekræftet, og der er ingen papirkurv.
+ Jeg bryder mig ikke om farverne på widget\'en, kan jeg ændre dem?
+ Jep. Når du føjer en widget til din skærm kommer muligheden for at konfigurere den frem. I nederste venstre hjørne finder du farvede kvadrater, klik på dem og vælg en ny farve. Du kan justere alfa med skyderen.
+ Kan jeg gendanne slettede filer?
+ Nej, ikke hvis de virkelig er slettet. Men du kan vælge at bruge en papirkurv i stedet for at slette noget direkte. Papirkurven aktiveres i indstillingerne.
+
+
+ Denne app bruger følgende 3.-parts biblioteker der gør at mit liv bliver lidt enklere. Tak.
+ 3.-parts licencer
+ Kotlin (programmeringssprog)
+ Subsampling Scale Image View (zoombar billedvisning)
+ Glide (billedindlæsning og -caching)
+ Picasso (billedindlæsning og -caching)
+ Android Image Cropper (beskær og roter billede)
+ RecyclerView MultiSelect (vælger flere elementer)
+ RtlViewPager (højre til venstre-swiping)
+ Joda-Time (Java-dato-erstatning)
+ Stetho (debugger databaser)
+ Otto (event bus)
+ PhotoView (zoombare GIF\'er)
+ PatternLockView (mønsterbeskyttelse)
+ Reprint (beskyttelse af fingeraftryk)
+ Gif Drawable (indlæser GIF\'er)
+ AutoFitTextView (skalering af tekst)
+ Robolectric (tester framework)
+ Espresso (tester helper)
+ Gson (JSON parser)
+ Leak Canary (detektering af pladsmangel)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-de/strings.xml b/commons/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..88a50f9
--- /dev/null
+++ b/commons/src/main/res/values-de/strings.xml
@@ -0,0 +1,560 @@
+
+ OK
+ Abbrechen
+ Speichern unter
+ Datei erfolgreich gespeichert
+ Ungültiges Dateiformat
+ Fehler: Zu wenig Speicher
+ Es ist ein Fehler aufgetreten: %s
+ Öffnen mit
+ Keine passende App gefunden
+ Festlegen als
+ Wert in Zwischenablage kopiert
+ Unbekannt
+ Immer
+ Nie
+ Details
+ Notizen
+ Lösche Ordner \'%s\'
+ nichts
+ Label
+
+
+ Favoriten
+ Favoriten hinzufügen
+ Zu Favoriten hinzufügen
+ Aus Favoriten entfernen
+
+
+ Suchen
+ Du musst mindestens 2 Zeichen eingeben, um die Suche zu starten.
+
+
+ Filter
+ Keine Elemente gefunden.
+ Filter ändern
+
+
+ Speicher-Berechtigung wird benötigt
+ Kontakte-Berechtigung wird benötigt
+ Kamera-Berechtigung wird benötigt
+ Audio-Berechtigung wird benötigt
+
+
+ Datei umbenennen
+ Ordner umbenennen
+ Konnte die Datei nicht umbenennen
+ Konnte den Ordner nicht umbenennen
+ Ordnername darf nicht leer sein
+ Ein Ordner mit diesem Namen existiert bereits
+ Root-Ordner des Speichers kann nicht umbenannt werden
+ Ordner erfolgreich umbenannt
+ Ordner umbenennen
+ Dateiname darf nicht leer sein
+ Dateiname enthält ungültige Zeichen
+ Dateiname \'%s\' anthält ungültige Zeichen
+ Dateiendung darf nicht leer sein
+ Quelldatei %s existiert nicht
+
+
+ Kopieren
+ Verschieben
+ Kopieren / Verschieben
+ Kopieren nach
+ Verschieben nach
+ Quelle
+ Ziel
+ Ziel auswählen
+ Klicken um Ziel auszuwählen
+ Konnte nicht auf ausgewähltes Ziel schreiben
+ Bitte Ziel auswählen
+ Quell- und Zielpfad sind identisch
+ Konnte die Datei(en) nicht kopieren
+ Kopiere…
+ Datei(en) erfolgreich kopiert
+ Es ist ein Fehler aufgetreten
+ Verschiebe…
+ Datei(en) erfolgreich verschoben
+ Einige Dateien konnten nicht verschoben werden
+ Einige Dateien konnten nicht kopiert werden
+ Keine Dateien ausgewählt
+ Speichern…
+ Ordner %s konnte nicht erstellt werden
+ Datei %s konnte nicht erstellt werden
+
+
+ Neuer Ordner
+ Ordner
+ Datei
+ Neuen Ordner erstellen
+ Ordner oder Datei mit diesem Namen existiert bereits
+ Der Name enthält unerlaubte Zeichen
+ Bitte einen Namen angeben
+ Ein unbekannter Fehler ist aufgetreten
+
+
+ Datei \"%s\" existiert bereits
+ Datei \"%s\" existiert bereits. Überschreiben?
+ Ordner \"%s\" existiert bereits
+ Zusammenführen
+ Überschreiben
+ Überspringen
+ \'_1\' anhängen
+ Bei allen Konflikten anwenden
+
+
+ Ordner auswählen
+ Datei auswählen
+ Bitte Zugriff auf externen Speicher bestätigen
+ Bitte am nächsten Bildschirm das Hauptverzeichnis deiner SD-Karte auswählen, um Schreibzugriff zu erlauben
+ Wenn die SD-Karte nicht angezeigt wird, versuche das
+ Auswahl bestätigen
+
+
+ - %d Element
+ - %d Elemente
+
+
+
+
+ - %d Element
+ - %d Elemente
+
+
+
+ - %d Element wird gelöscht
+ - %d Elemente werden gelöscht
+
+
+
+ Speicher auswählen
+ Intern
+ SD-Karte
+ Hauptverzeichnis
+ Falscher Ordner ausgewählt, bitte Hauptverzeichnis der SD-Karte auswählen
+ SDCard- und OTG-Gerätepfad dürfen nicht identisch sein
+ Die App wurde anscheinend auf der SD-Karte installiert, daher sind die Widgets nicht verfügbar. Sie werden auch in der Liste der Widgets nicht angezeigt.
+ Dies ist systemseitig so beschränkt. Für die Verwendung der Widgets muss die App wieder im internen Speicher installiert werden.
+
+
+ Eigenschaften
+ Pfad
+ Ausgewählte Elemente
+ Enthaltene Elemente (direkt)
+ Enthaltene Dateien (gesamt)
+ Auflösung
+ Dauer
+ Künstler
+ Album
+ Brennweite
+ Belichtungszeit
+ Filmempfindlichkeit
+ Blendenzahl
+ Kamera
+ EXIF
+ Liedtitel
+
+
+ Hintergrundfarbe
+ Schriftfarbe
+ Primäre Farbe
+ Vordergrundfarbe
+ App-Icon-Farbe
+ Standard wiederherstellen
+ Farbe ändern
+ Thema
+ Ändern einer Farbe setzt das Thema auf "Benutzerdefiniert"
+ Speichern
+ Verwerfen
+ Änderungen verwerfen
+ Bist du sicher, dass du die Änderungen verwerfen willst?
+ Du hast ungespeicherte Änderungen. Speichern vorm Beenden?
+ Farben bei allen schlichten Apps anwenden
+ Farben erfolgreich aktualisiert. Ein neues Thema \'Shared\' wurde hinzugefügt, bitte verwende dieses um für alle Apps die Farben zu ändern.
+
+ Simple Thank You um diese Funktion freizuschalten und die Entwicklung zu unterstützen. Danke!
+ ]]>
+
+
+
+ Hell
+ Dunkel
+ Solarisiert
+ Dunkelrot
+ Schwarz-Weiß
+ Benutzerdefiniert
+ Geteilt
+
+
+ Neue Funktionen
+ * Kleinere Verbesserungen werden nicht gelistet
+
+
+ Löschen
+ Entfernen
+ Umbenennen
+ Teilen
+ Teilen via
+ Alle auswählen
+ Verstecken
+ Nicht verstecken
+ Ordner verstecken
+ Ordner nicht verstecken
+ Verstecktes kurz zeigen
+ Verstecktes wieder verbergen
+ So viel Inhalt kann nicht auf einmal geteilt werden
+ Papierkorb leeren und deaktivieren
+ Rückgängig
+ Wiederholen
+
+
+ Sortieren nach
+ Name
+ Größe
+ Änderungsdatum
+ Aufnahmedatum
+ Titel
+ Dateiname
+ Dateiendung
+ Aufsteigend
+ Absteigend
+ Nur auf diesen Ordner anwenden
+
+
+ Willst du mit dem Löschen fortfahren?
+ Sollen %s Elemente wirklich gelöscht werden?
+ Sollen %s Elemente wirklich in den Papierkorb verschoben werden?
+ Soll dieses Element wirklich gelöscht werden?
+ Soll dieses Element wirklich in den Papierkorb verschoben werden?
+ Nicht erneut fragen (in dieser Sitzung)
+ Ja
+ Nein
+
+
+ - WARNUNG: Ein Ordner soll gelöscht werden
+ - WARNUNG: %d Ordner sollen gelöscht werden
+
+
+
+ PIN
+ PIN eingeben
+ Bitte eine PIN eingeben
+ Falsche PIN
+ PIN wiederholen
+ Muster
+ Muster zeichnen
+ Falsches Muster
+ Muster wiederholen
+ Fingerabdruck
+ Fingerabdruck hinzufügen
+ Bitte den Finger auf den Fingerabdrucksensor legen
+ Authentifizierung fehlgeschlagen
+ Authentifizierung blockiert, bitte in Kürze erneut versuchen
+ Keine Fingerabdrücke registriert, bitte in den Geräteeinstellungen hinzufügen
+ Einstellungen öffnen
+ Muster/PIN erfolgreich gespeichert. Falls vergessen, muss die App neu installiert werden.
+ Schutz erfolgreich eingerichtet. Falls beim Zurücksetzen Probleme auftreten, bitte die App neu installieren.
+
+
+ Gestern
+ Heute
+ Morgen
+ Sekunden
+ Minuten
+ Stunden
+ Tage
+
+
+ - %d Sekunde
+ - %d Sekunden
+
+
+ - %d Minute
+ - %d Minuten
+
+
+ - %d Stunde
+ - %d Stunden
+
+
+ - %d Tag
+ - %d Tage
+
+
+ - %d Woche
+ - %d Wochen
+
+
+ - %d Monat
+ - %d Monate
+
+
+ - %d Jahr
+ - %d Jahre
+
+
+
+
+ - %d Sekunde
+ - %d Sekunden
+
+
+ - %d Minute
+ - %d Minuten
+
+
+ - %d Stunde
+ - %d Stunden
+
+
+
+
+ - %d Sekunde vorher
+ - %d Sekunden vorher
+
+
+ - %d Minute vorher
+ - %d Minuten vorher
+
+
+ - %d Stunde vorher
+ - %d Stunden vorher
+
+
+ - %d Tag vorher
+ - %d Tage vorher
+
+
+
+
+ - %d Sekunde
+ - %d Sekunden
+
+
+ - %d Minute
+ - %d Minuten
+
+
+ - %d Stunde
+ - %d Stunden
+
+
+
+ Zeit bis Alarm:\n%s
+ Verbleibende Zeit bis Alarm ausgelöst wird:\n%s
+ Bitte stell sicher, dass der Alarm zuverlässig funktioniert, bevor Du Dich darauf verlässt. Es könnte wegen Systemeinschränkungen (z.B. Stromsparfunktion) fehlschlagen.
+ Bitte stell sicher, dass die Erinnerung zuverlässig funktioniert, bevor Du Dich darauf verlässt. Es könnte wegen Systemeinschränkungen (z.B. Stromsparfunktion) fehlschlagen.
+
+
+ Wecker
+ Snooze
+ Abstellen
+ Keine Erinnerung
+ Zu Beginn
+ Systemtöne
+ Deine Töne
+ Neuen Ton hinzufügen
+ No sound
+
+
+ Einstellungen
+ \"Simple Thank You\" App kaufen
+ Farben anpassen
+ Widget Farben anpassen
+ App in Englisch verwenden
+ Beim Start \"Neue Funktionen\" nicht anzeigen
+ Versteckte Elemente anzeigen
+ Schriftgröße
+ Klein
+ Mittel
+ Groß
+ Sehr groß
+ Versteckte Elemente mit Muster/PIN schützen
+ Gesamte App mit Passwort schützen
+ Beim Modifizieren von Dateien die alte Änderungszeit beibehalten
+ Beim Scrollen mit dem Scrollbalken Infofenster anzeigen
+ Verhindere, dass das Handy in den Ruhemodus wechselt, solange die App im Vordergrund läuft
+ Verwende keinen Löschen-bestätigen-Dialog
+ Ermögliche \"Auffrischen\" durch Ziehen von der oberen Kante
+ Benutze 24-Std.-Zeitformat
+ Beginne die Woche am Sonntag
+ Widgets
+ Immer dasselbe Snooze-Intervall nutzen
+ Schlummern-Intervall
+ Bei Tastendruck vibrieren
+ Elemente in den Papierkorb verschieben statt löschen
+ Intervall zum Leeren des Papierkorbs
+ Papierkorb leeren
+ Porträt-Modus erzwingen
+
+
+ Sichtbarkeit
+ Sicherheit
+ Scrollen
+ Dateioperationen
+ Papierkorb
+ Speichern
+ Beim Starten
+ Text
+
+
+ Diese Datei wiederherstellen
+ Markierte Dateien wiederherstellen
+ Alle Dateien wiederherstellen
+ Papierkorb erfolgreich geleert
+ Dateien erfolgreich wiederhergestellt
+ Papierkorb wirklich leeren? Die Dateien sind dann unwiederbringlich gelöscht.
+ Der Papierkorb ist leer
+
+
+ Importiere…
+ Exportiere…
+ Importieren erfolgreich
+ Exportieren erfolgreich
+ Importieren fehlgeschlagen
+ Exportieren fehlgeschlagen
+ Importieren einiger Einträge fehlgeschlagen
+ Exportieren einiger Einträge fehlgeschlagen
+ Keine Einträge zum Exportieren gefunden
+
+
+ OTG
+ Bitte den Basis-Ordner des OTG-Geräts auf der folgenden Seite wählen, um Zugriff zu gewähren
+ Falscher Ordner gewählt, bitte den Basis- (root-) Ordner des OTG-Geräts wählen
+
+
+ Januar
+ Februar
+ März
+ April
+ Mai
+ Juni
+ Juli
+ August
+ September
+ Oktober
+ November
+ Dezember
+
+
+ in Januar
+ in Februar
+ in März
+ in April
+ in Mai
+ in Juni
+ in Juli
+ in August
+ in September
+ in Oktober
+ in November
+ in Dezember
+
+ Montag
+ Dienstag
+ Mittwoch
+ Donnerstag
+ Freitag
+ Samstag
+ Sonntag
+
+ M
+ D
+ M
+ D
+ F
+ S
+ S
+
+ Mo
+ Di
+ Mi
+ Do
+ Fr
+ Sa
+ So
+
+
+ Über
+ Weitere schlichte Apps und Quellcode siehe
+ Vorschläge und Feedback bitte senden an
+ Weitere Apps
+ Drittanbieterlizenzen
+ Freunde einladen
+ Hey, wirf mal einen Blick auf %1$s: %2$s
+ Einladen via
+ Bewerte uns im Play Store
+ Spenden
+ Spenden
+ Folge uns
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Zusätzliche Infos
+ App-Version: %s
+ Betriebssystem: %s
+
+
+ Ich hoffe diese App gefällt dir. Sie beinhaltet keine Werbung, daher unterstütze bitte die Entwicklung durch den Kauf der Simple Thank You App. Dadurch wird auch dieser Dialog künftig nicht mehr angezeigt.
+ Vielen Dank!
+ ]]>
+
+ Kaufen
+ Bitte Simple Thank You auf die neueste Version aktualisieren
+ Vor einer Frage bitte die FAQ (häufig gestellte Fragen) lesen, oft findet sich dort bereits die Antwort.
+ Bitte lesen
+
+
+
+
+ nur eine kleine Info zu einer neu veröffentlichten App:
+ %2$s
+ Du kannst sie durch Drücken auf den Namen herunterladen.
+ Danke
+ ]]>
+
+
+
+ Häufig gestellte Fragen
+ Bevor du eine Frage stellst, bitte lies diese Seite:
+ Weshalb sehe ich das Widget dieser App nicht in der Liste der Widgets?
+ Wahrscheinlich, weil du die App auf die Speicherkarte verschoben hast. In diesem Fall werden durch eine Einschränkung von Android die Widgets dieser App versteckt. Die einzige Lösung ist die App über die Geräteeinstellungen wieder in den internen Speicher zu verschieben.
+ Ich will dich unterstützen, aber es ist mir nicht möglich Geld zu spenden. Gibt es eine andere Möglichkeit?
+ Ja, sicherlich. Du kannst die Apps weiterempfehlen oder gute Rückmeldung und Bewertungen geben. Du kannst ebenso helfen, indem du die Apps in neue Sprachen übersetzt oder vorhandene Übersetzungen aktualisierst.
+ Die Anleitung dazu findest du unter https://github.com/SimpleMobileTools/General-Discussion oder sende mir eine Nachricht an hello@simplemobiletools.com, falls du Hilfe benötigst.
+ Ich habe versehentlich einige Dateien gelöscht. Wie kann ich diese wiederherstellen?
+ Unglücklicherweise gar nicht. Dateien werden sofort nach dem Bestätigungsdialog gelöscht, sprich ein Papierkorb ist nicht vorhanden.
+ Ich mag die Farben des Widgets nicht. Kann ich sie wechseln?
+ Ja, wenn du ein Widget auf den Home Screen ziehst, erscheint ein Konfigurationsmenü. In der linken unteren Ecke sind farbige Kästchen, drücke auf sie und wähle eine neue Farbe. Ausserdem kannst du den Regler nutzen, um den Alphaparameter (Transparenz) einzustellen.
+ Kann ich irgendwie gelöschte Dateien wiederherstellen?
+ Wenn diese endgültig gelöscht wurden, nein. Aber Du kannst in den App-Eintellungen den Papierkorb aktivieren, statt des direkten Löschens. Das verschiebt zu löschende Dateien nur in den Papierkorb, und löscht diese nicht endgültig.
+
+
+ Diese App nutzt die folgenden Drittanbieterbibliotheken, die mein Leben einfacher machen. Danke!
+ Drittanbieterlizenzen
+ Kotlin (Programmiersprache)
+ Subsampling Scale Image View (Zoombare Bilder)
+ Glide (Bilder laden und zwischenspeichern)
+ Picasso (Bilder laden und zwischenspeichern)
+ Android Image Cropper (Bilder zuschneiden und rotieren)
+ RecyclerView MultiSelect (Mehrfachauswahl in Listen)
+ RtlViewPager (Rechts-Links-Wischen)
+ Joda-Time (Ersatz für Datums-Implementierung in Java)
+ Stetho (Datenbanken debuggen)
+ Otto (Event Bus)
+ PhotoView (Zoombare GIFs)
+ PatternLockView (Schutz mit Muster)
+ Reprint (Schutz mit Fingerabdruck)
+ Gif Drawable (Laden von GIFs)
+ AutoFitTextView (Textgröße ändern)
+ Robolectric (Test-Framework)
+ Espresso (Test-Hilfstool)
+ Gson (JSON-Parser)
+ Leak Canary (Memory-Leak-Detektor)
+ Number Picker (Konfigurierbarer Zahlenwähler)
+ ExoPlayer (Video-Player)
+ VR Panorama View (Panoramabilder anzeigen)
+ Apache Sanselan (Bild-Metadaten lesen)
+ Android Photo Filters (Bilder filtern)
+
diff --git a/commons/src/main/res/values-el/strings.xml b/commons/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..eb8b939
--- /dev/null
+++ b/commons/src/main/res/values-el/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Ακύρωση
+ Αποθήκευση ως
+ Αποθηκεύθηκε επιτυχώς
+ Μη έγκυρη μορφή αρχείου
+ Σφάλμα Έλλειψη μνήμης
+ Παρουσιάστηκε σφάλμα: %s
+ Άνοιγμα με
+ Δεν βρέθηκε έγκυρη εφαρμογή
+ Ορισμός ως
+ Η τιμή αντιγράφηκε στο πρόχειρο
+ Άγνωστο
+ Πάντα
+ Ποτέ
+ Λεπτομέρειες
+ Σημειώσεις
+ Διαγραφή φακέλου \'%s\'
+ Χωρίς
+ Label
+
+
+ Αγαπημένα
+ Προσθήκη αγαπημένων
+ Προσθήκη στα αγαπημένα
+ Αφαίρεση απο τα αγαπημένα
+
+
+ Αναζήτηση
+ Πληκτρολογήστε τουλάχιστον 2 χαρακτήρες για να ξεκινήσετε την αναζήτηση.
+
+
+ Φίλτρο
+ Δεν βρέθηκαν αντικείμενα.
+ Αλλαγή φίλτρου
+
+
+ Απαιτείται άδεια Αποθήκευσης
+ Απαιτείται άδεια Επαφών
+ Απαιτείται άδεια Κάμερας
+ Απαιτείται άδεια Ήχου
+
+
+ Μετονομασία αρχείου
+ Μετονομασία φακέλου
+ Αδύνατη η μετονομασία του αρχείου
+ Αδύνατη η μετονομασία του φακέλου
+ Το όνομα του φακέλου δεν πρέπει να είναι κενό
+ Ένας φάκελος με αυτό το όνομα υπάρχει ήδη
+ Αδύνατη η μετονομασία του ριζικού φακέλου ενός χώρου αποθήκευσης
+ Ο φάκελος μετονομάστηκε με επιτυχία
+ Μετονομασία φακέλου
+ Το όνομα αρχείου δεν μπορεί να είναι κενό
+ Το όνομα περιέχει μη έγκυρους χαρακτήρες
+ Το όνομα \'%s\' περιέχει μη έγκυρους χαρακτήρες
+ Η επέκταση δεν μπορεί να είναι κενή
+ To αρχείο προέλευσης %s δεν υπάρχει
+
+
+ Αντιγραφή
+ Μετακίνηση
+ Αντιγραφή / Μετακίνηση
+ Αντιγραφή σε
+ Μετακίνηση σε
+ Προέλευση
+ Προορισμός
+ Επιλογή προορισμού
+ Κάντε κλικ εδώ για να επιλέξετε προορισμό
+ Αδύνατη η εγγραφή στον επιλεγμένο προορισμό
+ Επιλέξτε έναν προορισμό
+ Η προέλευση και ο προορισμός δεν μπορούν να είναι ίδιες
+ Αδύνατη η αντιγραφή των αρχείων
+ Αντιγράφονται…
+ Τα αρχεία αντιγράφηκαν με επιτυχία
+ Ένα σφάλμα παρουσιάστηκε
+ Μετακινούνται…
+ Τα αρχεία μετακινήθηκαν με επιτυχία
+ Αδύνατη η μετακίνηση ορισμένων αρχείων
+ Αδύνατη η αντιγραφή ορισμένων αρχείων
+ Δεν έχουν επιλεγεί αρχεία
+ Αποθηκεύεται…
+ Αδύνατη η δημιουργία του φακέλου %s
+ Αδύνατη η δημιουργία του αρχείου %s
+
+
+ Δημιουργία νέου
+ Φάκελος
+ Αρχείο
+ Δημιουργία νέου φακέλου
+ Ένα αρχείο ή φάκελος με αυτό το όνομα υπάρχει ήδη
+ Το όνομα περιέχει μη αποδεκτούς χαρακτήρες
+ Παρακαλώ εισάγετε ένα όνομα
+ Παρουσιάστηκε άγνωστο σφάλμα
+
+
+ Το αρχείο \"%s\" υπάρχει ήδη
+ Το αρχείο \"%s\" υπάρχει ήδη. Αντικατάσταση?
+ Ο φάκελος \"%s\" υπάρχει ήδη
+ Συγχώνευση
+ Αντικατάσταση
+ Παράβλεψη
+ Προσάρτηση με \'_1\'
+ Εφαρμογή σε όλα
+
+
+ Επιλέξτε ένα φάκελο
+ Επιλέξτε ένα αρχείο
+ Επιβεβαιώστε την πρόσβαση στην εξωτερική αποθήκευση
+ Επιλέξτε τον ριζικό φάκελο της κάρτας SD στην επόμενη οθόνη, για να επιτρέψετε πρόσβαση εγγραφής
+ Εάν δεν βλέπετε την κάρτα SD, δοκιμάστε αυτό
+ Επιβεβαιώστε την επιλογή
+
+
+ - %d στοιχείο
+ - %d στοιχεία
+
+
+
+
+ - %d στοιχείο
+ - %d στοιχεία
+
+
+
+ - Διαγραφή %d στοιχείο
+ - Διαγραφή %d στοιχεία
+
+
+
+ Επιλέξτε αποθήκευση
+ Εσωτερική
+ SD Card
+ Root
+ Επιλέξατε λάθος φάκελο, επιλέξτε τον ριζικό φάκελο της SD card
+ SD card και OTG οι διαδρομές συσκευών δεν μπορούν να είναι οι ίδιες
+ Φαίνεται ότι έχετε εγκαταστήσει την εφαρμογή σε μια κάρτα SD, πράγμα που καθιστά τα γραφικά στοιχεία εφαρμογών μη διαθέσιμα. Δεν θα τα δείτε ακόμη και στη λίστα των διαθέσιμων γραφικών στοιχείων.
+ Είναι ένας περιορισμός του συστήματος, οπότε αν θέλετε να χρησιμοποιήσετε τα γραφικά στοιχεία, θα πρέπει να μετακινήσετε την εφαρμογή πίσω στον εσωτερικό αποθηκευτικό χώρο.
+
+
+ Ιδιότητες
+ Διαδρομή
+ Επολεγμένα στοιχεία
+ Απευθείας καταμέτρηση παιδιών
+ Αριθμός συνολικών αρχείων
+ Ανάλυση
+ Διάρκεια
+ Καλλιτέχνης
+ Αλμπουμ
+ Εστιακό μήκος
+ Χρόνος έκθεσης
+ Ταχύτητα ISO
+ F-αριθμός
+ Κάμερα
+ EXIF
+ Τίτλος τραγουδιού
+
+
+ Χρώμα φόντου
+ Χρώμα κειμένου
+ Πρωτεύον χρώμα
+ Χρώμα προσκηνίου
+ Χρώμα εικονιδίου εφαρμογής
+ Επαναφορά προεπιλογών
+ Αλλαγή χρώματος
+ Θέμα
+ Η αλλαγή ενός χρώματος θα λλάξει το Προσαρμοσμένο θέμα
+ Αποθήκευση
+ Απόρριψη
+ Αναίρεση αλλαγών
+ Είστε βέβαιοι ότι θέλετε να αναιρέσετε τις αλλαγές σας?
+ Έχετε αλλαγές που δεν έχουν αποθηκευτεί. Αποθήκευση πριν από την έξοδο?
+ Εφαρμογή χρωμάτων σε όλες τις Simple Apps
+ Τα χρώματα ενημερώθηκαν με επιτυχία. Ένα νέο θέμα που ονομάζεται \"Κοινόχρηστο\" έχει προστεθεί, παρακαλούμε να χρησιμοποιήστε το για την ενημέρωση των χρωμάτων όλων των εφαρμ. στο μέλλον.
+
+ Simple Thank You για να ξεκλειδώσετε αυτή τη λειτουργία και να υποστηρίξει την ανάπτυξη. Ευχαριστώ!
+ ]]>
+
+
+
+ Φωτεινό
+ Σκούρο
+ Solarized
+ Σκούρο κόκκινο
+ Μαύρο & Άσπρο
+ Προσαρμογή
+ Κοινόχρηστο
+
+
+ Τι νέο υπάρχει
+ * μόνο οι μεγαλύτερες ενημερώσεις παρατίθενται εδώ, υπάρχουν πάντα μερικές μικρότερες βελτιώσεις επίσης
+
+
+ Διαγραφή
+ Αφαίρεση
+ Μετονομασία
+ Κοινόχρηστο
+ Κοινόχρηστο μέσω
+ Επιλογή όλων
+ Απόκρυψη
+ Επανεμφάνιση
+ Απόκρυψη φακέλου
+ Επανεμφάνιση φακέλου
+ Προσωρινή Εμφάνιση κρυφών
+ Διακοπή εμφάνισης κρυφών μέσων
+ Δεν μπορείτε να μοιραστείτε μεγάλο περιεχόμενο ταυτόχρονα
+ Άδειασμα και απενεργοποίηση του Κάδου Ανακύκλωσης
+ Αναίρεση
+ Επανάληψη
+
+
+ Ταξινόμηση κατά
+ Όνομα
+ Μέγεθος
+ Τελευταία τροποποίηση
+ Ημερ. λήψης
+ Τίτλο
+ Όνομα αρχείου
+ Επέκταση
+ Αύξουσα
+ Φθίνουσα
+ Χρήση μόνο για αυτόν το φάκελο
+
+
+ Είστε βέβαιοι ότι θέλετε να συνεχίσετε με τη διαγραφή?
+ Είστε βέβαιοι ότι θέλετε να διαγράψετε %s?
+ Είστε βέβαιοι ότι θέλετε να μετακινήσετε %s στον κάδο ανακύκλωσης?
+ Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το στοιχείο?
+ Είστε βέβαιοι ότι θέλετε να μετακινήσετε αυτό το στοιχείο στον κάδο ανακύκλωσης?
+ Μην ξαναρωτήσετε σε αυτήν την περίοδο
+ Ναί
+ Όχι
+
+
+ - Προειδοποίηση: Διαγράφετε ένα φάκελο
+ - Προειδοποίηση: Διαγράφετε %d φακέλους
+
+
+
+ PIN
+ Εισαγωγή PIN
+ Παρακαλώ εισάγετε ένα PIN
+ Λάθος PIN
+ Επανάληψη PIN
+ Μοτίβο
+ Εισαγωγή μοτίβου
+ Λάθος μοτίβο
+ Επανάληψη μοτίβου
+ Δακτυλικό αποτύπωμα
+ Προσθήκη δακτυλικού αποτυπώματος
+ Παρακαλώ τοποθετήστε το δάκτυλό σας στον αισθητήρα δακτυλικών αποτυπωμάτων
+ Ο έλεγχος ταυτότητας απέτυχε
+ Αποκλεισμός ελέγχου ταυτότητας, προσπαθήστε ξανά σε λίγο
+ Δεν έχετε καταχωρήσει δακτυλικά αποτυπώματα, προσθέστε μερικά στις Ρυθμίσεις της συσκευής σας
+ Μετάβαση στις Ρυθμίσεις
+ Ο κωδικός πρόσβασης ολοκληρώθηκε με επιτυχία. Εγκαταστήστε ξανά την εφαρμογή σε περίπτωση που τον ξεχάσετε.
+ Η ρύθμιση προστασίας ολοκληρώθηκε με επιτυχία. Εγκαταστήστε ξανά την εφαρμογή σε περίπτωση προβλημάτων με την επαναφορά της.
+
+
+ Χθές
+ Σήμερα
+ Αύριο
+ δευτ.
+ λεπτ.
+ ώρ.
+ ημερ.
+
+
+ - %d δευτ.
+ - %d δευτ.
+
+
+ - %d λεπτ.
+ - %d λεπτ.
+
+
+ - %d ώρ.
+ - %d ώρ.
+
+
+ - %d ημερ.
+ - %d ημερ.
+
+
+ - %d εβδομ.
+ - %d εβδομ.
+
+
+ - %d μήνα
+ - %d μήνες
+
+
+ - %d χρόνο
+ - %d χρόνια
+
+
+
+
+ - σε %d δευτ.
+ - σε %d δευτ.
+
+
+ - σε %d λεπτ.
+ - σε %d λεπτ.
+
+
+ - σε %d ώρ.
+ - σε %d ώρ.
+
+
+
+
+ - πρίν %d δευτ.
+ - πρίν %d δευτ.
+
+
+ - πρίν %d λεπτ.
+ - πρίν %d λεπτ.
+
+
+ - πρίν %d ώρ.
+ - πρίν %d ώρ.
+
+
+ - πρίν %d ημερ.
+ - πρίν %d ημερ.
+
+
+
+
+ - αναβολή %d δευτ.
+ - αναβολή %d δευτ.
+
+
+ - αναβολή %d λεπτ.
+ - αναβολή %d λεπτ.
+
+
+ - αναβολή %d ώρ.
+ - αναβολή %d ώρ.
+
+
+
+ Χρόνος που απομένει μέχρι να κλείσει η ειδοποίηση:\n%s
+ Χρόνος που απομένει μέχρι να ενεργοποιηθεί η υπενθύμιση:\n%s
+ Βεβαιωθείτε ότι η ειδοποίηση λειτουργεί σωστά πριν βασιστείτε σε αυτήν. Θα μπορούσε να αποτύχει λόγω των περιορισμών του συστήματος που σχετίζονται με την εξοικονόμηση μπαταρίας.
+ Βεβαιωθείτε ότι η υπενθύμιση λειτουργεί σωστά πριν βασιστείτε σε αυτήν. Θα μπορούσε να αποτύχει λόγω των περιορισμών του συστήματος που σχετίζονται με την εξοικονόμηση μπαταρίας.
+
+
+ Ειδοποιήσεις
+ Αναβολή
+ Απόρριψη
+ Χωρίς υπενθύμιση
+ Κατά την έναρξη
+ Ήχοι συστήματος
+ Οι ήχοι σας
+ Προσθέστε νέο ήχο
+ Χωρίς ήχο
+
+
+ Ρυθμίσεις
+ Αγοράστε Simple σας ευχαριστώ
+ Προσαρμογή χρωμάτων
+ Προσαρμογή χρωμάτων widget
+ Χρήση English γλώσσας
+ Αποφύγετε την εμφάνιση, Τι νέο υπάρχει κατά την εκκίνηση
+ Εμφάνιση κρυφών στοιχείων
+ Μέγεθος κειμένου
+ Μικρό
+ Μεσαίο
+ Μεγάλο
+ Πολύ Μεγάλο
+ Κωδικός προστασίας προβολής κρυφών στοιχείων
+ Κωδικός προστασίας ολόκληρης της εφαρμογής
+ Διατηρήστε την παλιά τελευταία τροποποιημένη τιμή στην αντιγραφή/μετακίνηση/μετονομασία αρχείου
+ Εμφάνιση φυσαλίδας πληροφοριών κατά την κύλιση στοιχείων στη γραμμή κύλισης
+ Αποτροπή της αναστολής λειτουργίας του τηλεφώνου ενώ η εφαρμογή βρίσκεται στο προσκήνιο
+ Παράκαμψη πάντα το παράθυρο διαλόγου επιβεβαίωσης διαγραφής
+ Ενεργοποίηση ανανέωσης με Σύρετε απο πάνω-κάτω
+ Χρήση 24ώρου
+ Έναρξη εβδομάδας την Κυριακή
+ Widgets
+ Χρήση πάντα του ίδιου χρόνου αναβολής
+ Χρόνος αναβολής
+ Δόνηση κατά το πάτημα του κουμπιού
+ Μετακινήστε τα αντικείμενα στον Κάδο Ανακύκλωσης αντί για διαγραφή
+ Περίοδος καθαρισμού Κάδου ανακύκλωσης
+ Άδειασμα του Κάδου Ανακύκλωσης
+ Εξαναγκασμός σε Πορτρέτο
+
+
+ Ορατότητα
+ Ασφάλεια
+ Κύλιση
+ Λειτουργίες αρχείων
+ Κάδος Ανακύκλωσης
+ Εξοικονόμηση
+ Εκκίνηση
+ Κείμενο
+
+
+ Επαναφορά αυτού του αρχείου
+ Επαναφορά επιλεγμένων αρχείων
+ Επαναφορά όλων των αρχείων
+ Ο Κάδος Ανακύκλωσης έχει αδειάσει με επιτυχία
+ Τα αρχεία έχουν επαναφερθεί με επιτυχία
+ Είστε βέβαιοι ότι θέλετε να αδειάσετε τον Κάδο Ανακύκλωσης; Τα αρχεία θα χαθούν οριστικά.
+ Ο Κάδος Ανακύκλωσης είναι κενός
+
+
+ Εισαγωγή…
+ Εξαγωγή…
+ Επιτυχής Εισαγωγή
+ Επιτυχής Εξαγωγή
+ Η Εισαγωγή απέτυχε
+ Η Εξαγωγή απέτυχε
+ Η Εισαγωγή μερικών καταχωρήσεων απέτυχε
+ Η Εξαγωγή μερικών καταχωρήσεων απέτυχε
+ Δεν βρέθηκαν καταχωρήσεις για Εξαγωγή
+
+
+ OTG
+ Παρακαλώ επιλέξτε τον ριζικό φάκελο της συσκευής OTG στην επόμενη οθόνη, για να παραχωρήσετε πρόσβαση
+ Λάθος επιλεγμένο φάκελο, παρακαλώ επιλέξτε το ριζικό φάκελο της συσκευής OTG
+
+
+ Ιανουάριος
+ Φεβρουάριος
+ Μάρτιος
+ Απρίλιος
+ Μάιος
+ Ιούνιος
+ Ιούλιος
+ Αύγουστος
+ Σεπτέμβριος
+ Οκτώμβριος
+ Νοέμβριος
+ Δεκέμβριος
+
+
+ τον Ιανουάριο
+ τον Φεβρουάριο
+ τον Μάρτιο
+ τον Απρίλιο
+ τον Μάιο
+ τον Ιούνιο
+ τον Ιούλιο
+ τον Αύγουστο
+ τον Σεπτέμβριο
+ τον Οκτώμβριο
+ τον Νοέμβριο
+ τον Δεκέμβριο
+
+ Δευτέρα
+ Τρίτη
+ Τετάρτη
+ Πέμπτη
+ Παρασκευή
+ Σαββάτο
+ Κυριακή
+
+ Δ
+ Τ
+ Τ
+ Π
+ Π
+ Σ
+ Κ
+
+ Δευ
+ Τρί
+ Τετ
+ Πέμ
+ Παρ
+ Σαβ
+ Κυρ
+
+
+ Σχετικά
+ Για τον κώδικα επισκεφθείτε
+ Στείλτε τα σχόλιά σας ή τις προτάσεις σας
+ Περισσότερες εφαρμογές
+ Άδειες τρίτων
+ Προσκαλέσετε φίλους
+ Έι, έλα να δεις %1$s στο %2$s
+ Πρόσκληση μέσω
+ Βαθμολογήστε μας
+ Δωρεά
+ Δωρεά
+ Ακολουθήστε μας
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Πρόσθετες πληροφορίες
+ Έκδοση εφαρμογής: %s
+ OS Συσκευής: %s
+
+
+ Ελπίζω να απολαμβάνετε την εφαρμογή. Δεν περιέχει διαφημίσεις, παρακαλούμε να υποστηρίξει την ανάπτυξή της με την αγορά της Simple Thank You app, it will also prevent this dialog from showing up again.
+ Ευχαριστώ!
+ ]]>
+
+ Αγορά
+ Παρακαλώ ενημερώστε Simple Thank You στην τελευταία έκδοση
+ Πρίν κάνετε μια ερώτηση, παρακαλώ διαβάστε τις Συχνά Ερωτήσεις πρώτα, ίσως η λύση είναι εκεί.
+ Διάβασέ το
+
+
+
+
+ απλά σας ενημερώνω ότι πρόσφατα κυκλοφόρησε μια νέα εφαρμογή:
+ %2$s
+ Μπορείτε να την κατεβάσετε πατώντας τον τίτλο.
+ Ευχαριστώ
+ ]]>
+
+
+
+ Συχνές ερωτήσεις
+ Πριν κάνετε μια ερώτηση, διαβάστε πρώτα το
+ Γιατί δεν βλέπω αυτό τα widget εφαρμογών στη λίστα widgets;
+ Είναι πολύ πιθανό επειδή μετακινήσατε την εφαρμογή σε μια κάρτα SD. Υπάρχει ένας περιορισμός του συστήματος Android που αποκρύπτει τα συγκεκριμένα widget εφαρμογών
+ σε αυτή την περίπτωση. Η μόνη λύση είναι να μετακινήσετε την εφαρμογή πίσω στην εσωτερική αποθήκευση μέσω των ρυθμίσεων της συσκευής σας.
+ Θέλω να σας υποστηρίξω, αλλά δεν μπορώ να δωρίσω χρήματα. Υπάρχει κάτι άλλο που μπορώ να κάνω;
+ Ναι φυσικά. Μπορείτε να διαδώσετε τη λέξη σχετικά με τις εφαρμογές ή να δώσετε καλή ανατροφοδότηση και αξιολογήσεις. Μπορείτε επίσης να βοηθήσετε με τη μετάφραση των εφαρμογών σε μια νέα γλώσσα ή απλά να ενημερώσετε μερικές υπάρχουσες μεταφράσεις.
+ Μπορείτε να βρείτε τον οδηγό στο https://github.com/SimpleMobileTools/General-Discussion , ή απλά ένα μήνυμα στο hello@simplemobiletools.comαν χρειάζεσαι βοήθεια.
+ Διαγραφή κάποιων αρχείων κατά λάθος, πώς μπορώ να τα ανακτήσω;
+ Δυστυχώς, δεν μπορείτε. Τα αρχεία διαγράφονται αμέσως μετά το παράθυρο διαλόγου επιβεβαίωσης, δεν υπάρχει διαθέσιμος Κάδος απορριμμάτων.
+ Δεν μου αρέσουν τα χρώματα widget, μπορώ να τα αλλάξω;
+ Ναι, καθώς σύρετε ένα γραφικό στοιχείο στην αρχική οθόνη σας, εμφανίζεται μια οθόνη διαμόρφωσης widget. Θα δείτε έγχρωμα τετράγωνα στην κάτω αριστερή γωνία, απλά πατήστε τα για να επιλέξετε ένα νέο χρώμα. Μπορείτε να χρησιμοποιήσετε το ρυθμιστικό για να ρυθμίσετε το alpha επίσης.
+ Μπορώ να επαναφέρω με κάποιο τρόπο τα διαγραμμένα αρχεία;
+ Εάν έχουν πραγματικά διαγραφεί, δεν μπορείτε. Ωστόσο, μπορείτε να ενεργοποιήσετε τη χρήση του Κάδου Ανακύκλωσης αντί της διαγραφής στις ρυθμίσεις της εφαρμογής. Αυτό θα μετακινήσει απλώς τα αρχεία σε αυτόν αντί να τα διαγράψει.
+
+
+ Αυτή η εφαρμογή χρησιμοποιεί τις ακόλουθες βιβλιοθήκες τρίτων για να διευκολύνει τη ζωή μου. Ευχαριστώ.
+ Άδειες τρίτων
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-es/strings.xml b/commons/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..f6f1549
--- /dev/null
+++ b/commons/src/main/res/values-es/strings.xml
@@ -0,0 +1,560 @@
+
+ OK
+ Cancelar
+ Guardar como
+ Archivo guardado con éxito
+ Formato de archivo no válido
+ Error: sin memoria
+ Ocurrió un error: %s
+ Abrir con
+ No se encontró una aplicación válida
+ Establecer como
+ Valor copiado al portapapeles
+ Desconocido
+ Siempre
+ Nunca
+ Detalles
+ Notas
+ Eliminando carpeta \'%s\'
+ Ninguno
+ Label
+
+
+ Favoritos
+ Agregar a favoritos
+ Agregar a favoritos
+ Eliminar de favoritos
+
+
+ Buscar
+ Escribe por lo menos dos caracteres para iniciar la búsqueda.
+
+
+ Filtrar
+ No se encontraron artículos.
+ Cambiar filtro
+
+
+ Se requiere permiso de almacenamiento
+ Se requiere permiso de Contactos
+ Se requiere permiso para la Cámara
+ Se requiere permiso de Audio
+
+
+ Renombrar archivo
+ Renombrar carpeta
+ No se pudo renombrar el archivo
+ No se pudo renombrar la carpeta
+ El nombre de la carpeta no puede estar vacío
+ Ya existe una carpeta con ese nombre
+ No se puede cambiar el nombre de la carpeta raíz del almacenamiento
+ Carpeta renombrada correctamente
+ Renombrando carpeta
+ El nombre del archivo no puede estar vacío
+ El nombre del archivo contiene caracteres no válidos
+ El nombre del archivo \'%s\' contiene caracteres no válidos
+ La extensión no puede estar vacía
+ El archivo %s no existe
+
+
+ Copiar
+ Mover
+ Copiar/Mover
+ Copiar a
+ Mover a
+ Origen
+ Destino
+ Seleccionar destino
+ Pulsa aquí para elegir el destino
+ No se pudo escribir en el destino elegido
+ Por favor elige un destino
+ Origen y destino no pueden coincidir
+ No se pudieron copiar los archivos
+ Copiando…
+ Archivos copiados correctamente
+ Ha ocurrido un error
+ Moviendo…
+ Archivos movidos correctamente
+ Algunos archivos no se pudieron mover
+ Algunos archivos no se pudieron copiar
+ No hay archivos seleccionados
+ Guardando…
+ No se puede crear la carpeta %s
+ No se puede crear el fichero %s
+
+
+ Crear nueva
+ Carpeta
+ Archivo
+ Crear nueva carpeta
+ Ya existe un archivo o carpeta con ese nombre
+ El nombre contiene caracteres no válidos
+ Por favor introduce un nombre
+ Ocurrió un error desconocido
+
+
+ El archivo \"%s\" ya existe
+ El archivo \"%s\" ya existe. Sobreescribir?
+ La carpeta \"%s\" ya existe
+ Fusionar
+ Sobreescribir
+ Saltar
+ Añadir \'_1\'
+ Aplicar a todo
+
+
+ Seleccione una carpeta
+ Seleccione un archivo
+ Permita el acceso al almacenamiento externo
+ Por favor, elija la carpeta raíz de la tarjeta SD en la próxima pantalla para conceder el acceso de escritura
+ Si no ve la tarjeta SD, prueba esto
+ Confirmar la selección
+
+
+ - %d elemento
+ - %d elementos
+
+
+
+
+ - %d elemento
+ - %d elementos
+
+
+
+ - Eliminando %d elemento
+ - Eliminando %d elementos
+
+
+
+ Seleccione almacenamiento
+ Almacenamiento interno
+ Tarjeta SD
+ Raíz /
+ Se seleccionó la carpeta incorrecta, seleccione la carpeta raíz de su tarjeta SD
+ La targeta SD i el dispositivo OTG no pueden ser los mismos
+ Parece que tienes la aplicación instalada en una tarjeta SD, lo que hace que los widgets de la aplicación no estén disponibles. Ni siquiera los verás en la lista de widgets disponibles.
+ Es una limitación del sistema, por lo que si deseas usar los widgets, debes volver a colocar la aplicación en el almacenamiento interno.
+
+
+ Propiedades
+ Ubicación
+ Elementos seleccionados
+ Número de archivos directamente contenidos
+ Número total de archivos
+ Resolución
+ Duración
+ Artista
+ Álbum
+ Longitud focal
+ Tiempo de exposición
+ Velocidad ISO
+ F-number
+ Cámara
+ EXIF
+ Título de la canción
+
+
+ Color del fondo
+ Color del texto
+ Color principal
+ Color de primer plano
+ Color del icono de la aplicación
+ Valores por defecto
+ Cambiar de color
+ Tema
+ Cambiar un color hará que cambie a tema personalizado
+ Guardar
+ Deshacer
+ Deshacer cambios
+ ¿Seguro que quiere deshacer los cambios?
+ Tiene cambios sin aplicar. ¿Guardar antes de salir?
+ Aplicar colores a todas las aplicaciones Simple Apps
+ Colores actualizados correctamente. Se ha añadido un nuevo tema llamado \'Shared\', Por favor utilicelo para actualizar los colores de todas las aplicaciones en el futuro.
+
+ Simple Thank You para desbloquear esta función y ayudar al desarrollo. Gracias!
+ ]]>
+
+
+
+ Claro
+ Oscuro
+ Solarized
+ Rojo Oscuro
+ Blanco & Negro
+ Personalizado
+ Compartido
+
+
+ Novedades
+ * Sólo las actualizaciones más grandes se enumeran aquí, siempre hay algunas pequeñas mejoras también
+
+
+ Eliminar
+ Renombrar
+ Compartir
+ Compartir con
+ Seleccionar todo
+ Ocultar
+ Mostrar
+ Ocultar carpeta
+ Mostrar carpeta
+ Mostrar ocultos temporalmente
+ No mostrar ocultos
+ No puedes compartir todo este contenido a la vez
+ Vaciar y desactivar la papelera de reciclaje
+ Deshacer
+ Rehacer
+
+
+ Ordenar por
+ Nombre
+ Tamaño
+ Modificación
+ Creación
+ Título
+ Nombre del archivo
+ Extensión
+ Ascendente
+ Descendente
+ Sólo para esta carpeta
+
+
+ ¿Está seguro de querer continuar con la eliminación?
+ Estas seguro que quieres borrar %s?
+ ¿Estás seguro de que quieres mover %s a la papelera de reciclaje?
+ ¿Seguro que quieres eliminar este elemento?
+ ¿Seguro que quieres enviar este elemento a la papelera?
+ No volver a preguntar en esta sesión
+ Sí
+ No
+
+
+ - ADVERTENCIA: está eliminando una carpeta
+ - ADVERTENCIA: está eliminando %d carpetas
+
+
+
+ PIN
+ Introduzca PIN
+ Por favor introduzca PIN
+ PIN erróneo
+ Repetir PIN
+ Patrón
+ Inserte Patrón
+ Patrón erroneo
+ Repetir Patrón
+ Huella dactilar
+ Añadir Huella dactilar
+ Por favor pon el dedo en el lector de Huella dactilares
+ Autenticación fallida
+ Autenticación bloqueada, por favor intente de nuevo en un momento
+ No tiene huellas dactilares registradas, por favor agregue algunas en la Configuración de su dispositivo
+ Ir a la configuración
+ Se ha agregado la contraseña con éxito. Por favor, vuelva a instalar la aplicación en caso de que la olvide.
+ Configuración de protección con éxito. Por favor, vuelva a instalar la aplicación en caso de problemas al restablecerla.
+
+
+ Ayer
+ Hoy
+ Mañana
+ segundos
+ minutos
+ horas
+ días
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+ - %d día
+ - %d días
+
+
+ - %d semana
+ - %d semanas
+
+
+ - %d mes
+ - %d meses
+
+
+ - %d año
+ - %d años
+
+
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+
+
+ - %d segundo antes
+ - %d segundos antes
+
+
+ - %d minuto antes
+ - %d minutos antes
+
+
+ - %d hora antes
+ - %d horas antes
+
+
+ - %d día antes
+ - %d días antes
+
+
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+
+ Tiempo restante hasta que suene la alarma:\n%s
+ Tiempo restante hasta que se active el recordatorio:\n%s
+ Asegúrese de que la alarma funcione correctamente antes de confiar en ella. Podría comportarse mal debido a las restricciones del sistema relacionadas con el ahorro de batería.
+ Asegúrese de que los recordatorios funcionen correctamente antes de confiar en ellos. Podrían portarse mal debido a las restricciones del sistema relacionadas con el ahorro de batería.
+
+
+ Alarma
+ Posponer
+ Cancelar
+ Sin recordatorio
+ Al inicio
+ Sonidos del sistema
+ Tus sonidos
+ Agrega un nuevo sonido
+ Sin sonido
+
+
+ Ajustes
+ Compra Simple Thank You
+ Cambiar colores
+ Personaliza los colores del widget
+ Usar el idioma Inglés
+ Permitir mostrar novedades al iniciar
+ Mostrar elementos ocultos
+ Tamaño de fuente
+ Pequeña
+ Mediana
+ Grande
+ Enorme
+ Contraseña para proteger los medios ocultos
+ Contraseña para proteger toda la aplicación
+ Mantener el valor de la última modificación al copiar/mover/renombrar los archivos
+ Mostrar una burbuja de información al desplazar elementos arrastrando la barra de desplazamiento
+ No dejar dormir el teléfono con la aplicación en primer plano
+ Omitir siempre el diálogo de confirmación de eliminación
+ Habilitar tirar desde la parte superior para refrescar
+ Usar el formato 24 horas
+ Comenzar la semana en Domingo
+ Widgets
+ Siempre use el mismo tiempo de repetición
+ Tiempo de repetición
+ Vibrar al presionar un botón
+ Mover los elementos en la Papelera de reciclaje en lugar de eliminar
+ Intervalo de vaciado de la papelera de reciclaje
+ Vaciar papelera de reciclaje
+ Forzar el modo retrato
+
+
+ Visibilidad
+ Seguridad
+ Desplazamiento
+ Operaciones con ficheros
+ Papelera de reciclaje
+ Guardando
+ Puesta en marcha
+ Texto
+
+
+ Restaurar el fichero
+ Restaurar los ficheros seleccionados
+ Restaurar todos los ficheros
+ La papelera se ha vaciado
+ Lo ficheros han sido restaurados correctamente
+ Seguro que quieres vaciar la papelera? Los ficheros se perderan definitivamente.
+ La papelera está vacia
+
+
+ Importando…
+ Exportando…
+ Importación correcta
+ Exportación correcta
+ Importación fallida
+ Exportación fallida
+ La importación de algunas entradas ha fallado
+ La exportación de algunas entradas ha fallado
+ No se han encontrado entradas para exportar
+
+
+ OTG
+ Elija la carpeta raíz del dispositivo OTG en la pantalla siguiente, para otorgar acceso
+ Carpeta incorrecta, seleccione la carpeta raíz de su dispositivo OTG
+
+
+ Enero
+ Febrero
+ Marzo
+ Abril
+ Mayo
+ Junio
+ Julio
+ Agosto
+ Septiembre
+ Octubre
+ Noviembre
+ Diciembre
+
+
+ en Enero
+ en Febrero
+ en Marzo
+ en Abril
+ en Mayo
+ en Junio
+ en Julio
+ en Agosto
+ en Septiembre
+ en Octubre
+ en Noviembre
+ en Diciembre
+
+ Lunes
+ Martes
+ Miércoles
+ Jueves
+ Viernes
+ Sábado
+ Domingo
+
+ L
+ M
+ X
+ J
+ V
+ S
+ D
+
+ Lun
+ Mar
+ Mié
+ Jue
+ Vie
+ Sáb
+ Dom
+
+
+ Acerca de
+ Más aplicaciones sencillas y código fuente en
+ Envía tus comentarios o sugerencias a
+ Más aplicaciones
+ Licencias de terceros
+ Invitar a amigos
+ Hola, ven y mira %1$s en %2$s
+ Invitar con
+ Vótanos en Play Store
+ Donar
+ Donar
+ Síguenos
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Información adicional
+ App version: %s
+ Device OS: %s
+
+
+ Espero que estés disfrutando de la aplicación. No contiene anuncios, por lo que debe respaldar su desarrollo comprando la aplicación Simple Thank You , también evitará que este diálogo vuelva a aparecer.
+ Gracias!
+ ]]>
+
+ Comprar
+ Por favor, actualice Simple Tank You a la última versión
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ solo te informamos que recientemente se ha lanzado una nueva aplicación:
+ %2$s
+ Puedes descargarla presionando el título.
+ Gracias
+ ]]>
+
+
+
+ Preguntas frecuentes
+ Antes de hacer una pregunta, primero lea las
+ ¿Cómo es que no veo este widget de aplicaciones en la lista de widgets?
+ Lo más probable es que haya movido la aplicación en una tarjeta SD. Hay una limitación del sistema Android que oculta los widgets de la aplicación dada
+ en ese caso. La única solución es volver a colocar la aplicación en el almacenamiento interno a través de la configuración de su dispositivo.
+ Quiero apoyarte, pero no puedo donar dinero. ¿Hay algo mas que pueda hacer?
+ Sí, por supuesto. Puede correr la voz sobre las aplicaciones o dar buenos comentarios y valoraciones. También puede ayudar traduciendo las aplicaciones en un nuevo idioma o simplemente actualizar algunas traducciones existentes.
+ Puede encontrar la guía en https://github.com/SimpleMobileTools/General-Discussion, o simplemente envíeme un mensaje a hello@simplemobiletools.com si necesita ayuda.
+ Borré algunos archivos por error, ¿cómo puedo recuperarlos?
+ Tristemente, no puedes. Los archivos se eliminan instantáneamente después del diálogo de confirmación, no hay papelera disponible.
+ No me gustan los colores del widget, ¿puedo cambiarlos?
+ Sí, al arrastrar un widget en la pantalla de inicio aparece una pantalla de configuración de widgets. Verá cuadrados de colores en la esquina inferior izquierda, solo presione para elegir un nuevo color. Puede usar el control deslizante para ajustar el alfa también.
+ ¿Puedo de alguna manera restaurar archivos borrados?
+ Si realmente fueron eliminados, no puedes. Sin embargo, puedes habilitar el uso de una papelera de reciclaje en lugar de eliminar en la configuración de la aplicación. Eso solo moverá los archivos en lugar de eliminarlos.
+
+
+ Esta aplicación usa las siguientes bibliotecas de terceros que hacen mi vida más fácil. Gracias.
+ Licencias de terceros
+ Kotlin (Programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Selector de número (selector de número personalizable)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-fi/strings.xml b/commons/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..ea8d176
--- /dev/null
+++ b/commons/src/main/res/values-fi/strings.xml
@@ -0,0 +1,560 @@
+
+ OK
+ Peruuta
+ Tallenna nimellä
+ Tallennettu
+ Virheellinen tiedostomuoto
+ Liian vähän muistia
+ Tapahtui virhe: %s
+ Avaa sovelluksessa
+ Sovelluksia ei löydetty
+ Aseta
+ Kopioitu leikepöydälle
+ Tuntematon
+ Aina
+ Ei koskaan
+ Yksityiskohdat
+ Muistiinpanot
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Favorites
+ Add favorites
+ Add to favorites
+ Remove from favorites
+
+
+ Haku
+ Syötä ainakin 2 merkkiä hakeaksesi
+
+
+ Suodata
+ Tiedostoja ei löytynyt.
+ Vaihda suodatin
+
+
+ Lupa tallennustilaan vaaditaan
+ Lupa yhteystietoihin vaaditaan
+ Lupa kameraan vaaditaan
+ Lupa ääneen vaaditaan
+
+
+ Uudelleennimeä tiedosto
+ Uudelleennimeä kansio
+ Tiedostoa ei voitu uudelleennimetä
+ Kansiota ei voitu uudelleennimetä
+ Kansiolle täytyy antaa nimi
+ Nimi on jo käytössä
+ Root-kansiota ei voi uudelleennimetä
+ Kansio uudelleennimetty
+ Uudelleennimetään kansio
+ Tiedostolle täytyy antaa nimi
+ Tiedostonimi sisältää kiellettyjä merkkejä
+ Tiedostonimi \'%s\' sisältää kiellettyjä merkkejä
+ Tiedostomuoto ei voi olla tyhjä
+ Lähdetiedostoa %s ei löydy
+
+
+ Kopioi
+ Siirrä
+ Kopioi / Siirrä
+ Kopioi kohteeseen
+ Siirrä kohteeseen
+ Lähde
+ Kohde
+ Valitse kohde
+ Kosketa valitaksesi kohteen
+ Valittuun kohteeseen ei voitu kirjoittaa
+ Valitse kohde
+ Lähde ja kohde eivät voi olla samat
+ Tiedostoja ei voitu kopioida
+ Kopioidaan
+ Tiedostot kopioitu
+ Tapahtui virhe
+ Siirretään
+ Tiedostot siirretty
+ Kaikkia tiedostoja ei voitu siirtää
+ Kaikkia tiedostoja ei voitu kopioida
+ Ei valittuja tiedostoja
+ Tallennetaan
+ Ei voitu luoda kansiota %s
+ Ei voitu luoda tiedostoa %s
+
+
+ Luo uusi
+ Kansio
+ Tiedosto
+ Luo uusi kansio
+ Samanniminen tiedosto tai kansio on jo olemassa
+ Nimessä on kiellettyjä merkkejä
+ Syötä nimi
+ Tapahtui tuntematon virhe
+
+
+ Tiedosto \"%s\" on jo olemassa
+ Tiedosto \"%s\" on jo olemassa. Korvataanko?
+ Kansio \"%s\" on jo olemassa
+ Yhdistä
+ Korvaa
+ Ohita
+ Lisää nimeen \'_1\'
+ Käytä kaikkiin
+
+
+ Valitse kansio
+ Valitse tiedosto
+ Vahvista pääsy ulkoiseen tallennustilaan
+ Valitse muistikortin root-kansio seuraavaksi antaaksesi kirjoitusoikeuden
+ Jos et näe muistikorttia, kokeile tätä
+ Vahvista valinta
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Valitse tallennustila
+ Sisäinen
+ Muistikortti
+ Root
+ Väärä kansio valittu, valitse muistikortin root-kansio
+ Muistikortin ja OTG-laitteen polut eivät voi olla samat
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Ominaisuudet
+ Polku
+ Kohteita valittu
+ Sisältyvien kohteiden määrä
+ Tiedostoja yhteensä
+ Resoluutio
+ Kesto
+ Esittäjä
+ Albumi
+ Polttoväli
+ Valotusaika
+ ISO-arvo
+ F-arvo
+ Kamera
+ EXIF
+ Kappaleen nimi
+
+
+ Taustaväri
+ Tekstin väri
+ Ensisijainen väri
+ Etualan väri
+ Sovelluksen ikonin väri
+ Palauta oletukset
+ Change color
+ Teema
+ Värin vaihtaminen vaihtaa teemaksi "Oma teema"
+ Tallenna
+ Hylkää
+ Peru muutokset
+ Haluatko varmasti perua muutokset?
+ Sinulla on tallentamattomia muutoksia. Tallennetaanko ennen sulkemista?
+ Käytä värejä kaikissa Simple App -sovelluksissa
+ Värit päivitetty. Uusi teema nimeltä \'Jaettu\' käytä sitä jatkossa muokataksesi värejä kaikissa sovelluksissa.
+
+ Simple Thank You avataksesi ominaisuuden ja tukeaksesi kehitystä. Kiitos!
+ ]]>
+
+
+
+ Vaalea
+ Tumma
+ Solarized
+ Tumman punainen
+ Musta & Valkoinen
+ Oma
+ Jaettu
+
+
+ Mitä uutta
+ * tässä mainitaan vain isot muutokset, ei pieniä korjauksia
+
+
+ Poista
+ Poista
+ Uudelleennimeä
+ Jaa
+ Jaa sovelluksella
+ Valitse kaikki
+ Piilota
+ Näytä piilotetut
+ Piilota kansio
+ Näytä piilotettu kansio
+ Näytä piilotetut väliaikaisesti
+ Lopeta piilotettujen näyttäminen
+ Et voi jakaa näin paljoa kerralla
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Järjestä
+ Nimen perusteella
+ Koon perusteella
+ Viimeksi muokattu
+ Viimeksi kuvattu
+ Otsikko
+ Tiedostonimi
+ Extension
+ Nouseva
+ Laskeva
+ Käytä vain tässä kansiossa
+
+
+ Haluatko varmasti poistaa pysyvästi?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Älä kysy uudestaan tällä istunnolla
+ Poista
+ Peruuta
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Syötä PIN
+ Syötä PIN
+ Väärä PIN
+ Toista PIN
+ Kuvio
+ Syötä kuvio
+ Väärä kuvio
+ Toista kuvio
+ Sormenjälki
+ Lisää sormenjälki
+ Aseta sormi sormenjälkitunnistimelle
+ Kirjautuminen epäonnistui
+ Kirjautuminen estetty, yritä myöhemmin uudelleen
+ Sinulla ei ole tallennettuja sormenjälkiä, lisää niitä laitteesi asetuksista
+ Mene asetuksiin
+ Salasana tallennettu. Asenna sovellus uudelleen jos unohdat sen.
+ Suojaus onnistui. Asenna sovellus uudelleen jos kohtaat ongelmia.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minuutit
+ tunnit
+ päivät
+
+
+ - %d sekunti
+ - %d sekuntia
+
+
+ - %d minuutti
+ - %d minuuttia
+
+
+ - %d tunti
+ - %d tuntia
+
+
+ - %d päivä
+ - %d päivää
+
+
+ - %d viikko
+ - %d viikkoa
+
+
+ - %d kuukausi
+ - %d kuukautta
+
+
+ - %d vuosi
+ - %d vuotta
+
+
+
+
+ - %d sekunnissa
+ - %d sekunnissa
+
+
+ - %d minuutissa
+ - %d minuutissa
+
+
+ - %d tunnissa
+ - %d tunnissa
+
+
+
+
+ - %d sekunti ennen
+ - %d sekuntia ennen
+
+
+ - %d minuutti ennen
+ - %d minuuttia ennen
+
+
+ - %d tunti ennen
+ - %d tuntia ennen
+
+
+ - %d päivä ennen
+ - %d päivää ennen
+
+
+
+
+ - %d sekunnilla
+ - %d sekunnilla
+
+
+ - %d minuutilla
+ - %d minuutilla
+
+
+ - %d tunnilla
+ - %d tunnilla
+
+
+
+ Aikaa hälytykseen:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Varmista että hälytys toimii ennenkuin turvaudut siihen. Se voi toimia virheellisesti laitteesi virransäästötoiminnoista riippuen.
+ Varmista että muistutus toimii ennenkuin turvaudut siihen. Se voi toimia virheellisesti laitteesi virransäästötoiminnoista riippuen.
+
+
+ Alarm
+ Torkuta
+ Ohita
+ Ei muistutusta
+ Alussa
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Asetukset
+ Purchase Simple Thank You
+ Muokkaa sovelluksen värejä
+ Customize widget colors
+ Käytä Englannin kieltä
+ Älä näytä uutisia aloituksen yhteydessä
+ Näytä piilotetut tiedostot
+ Fonttikoko
+ Pieni
+ Keskikokoinen
+ Suuri
+ Erittäin suuri
+ Suojaa piilotetut salasanalla
+ Suojaa koko sovellus salasanalla
+ Säilytä alkuperäinen "viimeksi muokattu" ajankohta tiedostoa siirtäessä, kopioitaessa ja uudelleennimettäessä
+ Näytä lisätietokupla vierityspalkista vierittäessä
+ Estä puhelinta siirtymästä virransäästöön kun sovellus on avattu ja käytössä
+ Älä koskaan kysy varmistusta tiedostoja poistaessa
+ Päivitä näkymä vedettäessä ylhäältä alas
+ Käytä 24:n tunnin kelloa
+ Aloita viikko sunnuntaista
+ Widgetit
+ Käytä aina samaa torkkuaikaa
+ Torkkuaika
+ Tärinä koskettaessa
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Näkyvyys
+ Turvallisuus
+ Vierittäminen
+ Tiedostot
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Tuodaan
+ Viedään
+ Tuonti onnistui
+ Vienti onnistui
+ Tuonti epäonnistui
+ Vienti epäonnistui
+ Joidenkin kohteiden tuonti epäonnistui
+ Joidenkin kohteiden vienti epäonnistui
+ Vientikohteita ei löydetty
+
+
+ OTG
+ Valitse OTG-laitteen root-kansio salliaksesi pääsyn
+ Väärä kansio valittu, valitse OTG-laitteen root-kansio
+
+
+ Tammikuu
+ Helmikuu
+ Maaliskuu
+ Huhtikuu
+ Toukokuu
+ Kesäkuu
+ Heinäkuu
+ Elokuu
+ Syyskuu
+ Lokakuu
+ Marraskuu
+ Joulukuu
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Maanantai
+ Tiistai
+ Keskiviikko
+ Torstai
+ Perjantai
+ Lauantai
+ Sunnuntai
+
+ M
+ T
+ K
+ T
+ P
+ L
+ S
+
+ Maa
+ Tii
+ Kes
+ Tor
+ Per
+ Lau
+ Sun
+
+
+ Tietoa
+ Lähdekoodin löydät täältä
+ Lähetä palautteesi ja ehdotuksia osoitteeseen
+ Lisää sovelluksia
+ Kolmansien osapuolien lisenssit
+ Kutsu ystäviä
+ Hei, tutustu %1$s täällä %2$s
+ Kutsu sovelluksella
+ Arvostele
+ Lahjoita
+ Lahjoita
+ Seuraa
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Lisätietoa
+ Sovelluksen versio: %s
+ Laitteen käyttöjärjestelmä: %s
+
+
+ toivottavasti pidät sovelluksesta. Se ei sisällä mainoksia, tue sen kehitystä ostamalla Simple Thank You sovellus, tämä myös estää tämän ilmoituksen jatkossa.
+ Kiitos!
+ ]]>
+
+ Osta
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ halusin vain kertoa että uusi sovellus on julkaistu:
+ %2$s
+ Voit ladata sen koskettamalla otsikkoa.
+ Kiitos!
+ ]]>
+
+
+
+ Usein kysytyt kysymykset
+ Before you ask a question, please first read the
+ Miksi en näe tämän sovelluksen widgettiä widget-valikossa?
+ Todennäköisesti asensit sovelluksen ulkoiselle muistikortille. Tässä tapauksessa ainoa vaihtoehto on uudelleenasentaa sovellus laitteen sisäiseen tallennustilaan.
+ Haluan tukea sinua, mutta minulla ei ole varaa lahjoittaa. Voinko auttaa jotenkin muuten?
+ Tottakai. Voit kertoa ystävillesi sovelluksista ja antaa hyviä arvosteluja ja palautetta. Voit myös auttaa kääntämällä sovelluksen uudelle kielelle tai korjata käännösvirheitä.
+ Ohjeet löydät täältä https://github.com/SimpleMobileTools/General-Discussion , tai lähettämällä viestiä osoitteeseen hello@simplemobiletools.com jos Sinulla on kysyttävää.
+ Poistin tiedostoja vahingossa, miten palautan ne?
+ Valitettavasti se ei ole mahdollista. Tiedostot poistetaan aina pysyvästi.
+ En pidä widgetin väreistä, voinko vaihtaa ne?
+ Kyllä. Kun luot widgetin aloitusnäytölle aukeaa widgetin määritysikkuna. Paina värillisiä neliöitä valitaksesi uudet värit. Vierityspalkista voit myös säätää läpinäkyvyyttä.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ Tämä sovellus käyttää kolmannen osapuolen lisenssejä tehdäkseni elämästäni helpompaa. Kiitos.
+ Kolmansien osapuolien lisenssit
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-fr/strings.xml b/commons/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..2b9844c
--- /dev/null
+++ b/commons/src/main/res/values-fr/strings.xml
@@ -0,0 +1,559 @@
+
+ Ok
+ Annuler
+ Enregistrer sous
+ Fichier sauvegardé avec succès
+ Format de fichier invalide
+ Erreur excès de mémoire
+ Une erreur est survenue: %s
+ Ouvrir avec
+ Aucune application valide trouvée
+ Définir comme
+ Valeur copiée dans le presse-papier
+ Inconnu
+ Toujours
+ Jamais
+ Détails
+ Notes
+ Supprimer le dossier \'%s\'
+ None
+ Label
+
+
+ Favoris
+ Ajouter des favoris
+ Ajouter aux favoris
+ Enlever des favoris
+
+
+ Recherche
+ Tapez au moins 2 caractères pour lancer la recherche.
+
+
+ Filtrer
+ Aucun élément trouvé.
+ Changer le filtre
+
+
+ L\'autorisation d\'accès au stockage est requise
+ L\'autorisation d\'accès aux contacts est requise
+ L\'autorisation d\'accès à l\'appareil photo est requise
+ L\'autorisation d\'accès aux paramètres audios est requise
+
+
+ Renommer le fichier
+ Renommer le dossier
+ Impossible de renommer le fichier
+ Impossible de renommer le dossier.
+ Le nom de dossier ne peut pas être vide.
+ Un dossier homonyme existe déjà.
+ Impossible de renommer le dossier racine de la mémoire.
+ Dossier renommé avec succès
+ Renommage du dossier
+ Le nom de fichier ne peut pas rester vide
+ Le nom de fichier contient des caractères invalides
+ Le nom de fichier \'%s\' contient des caractères invalides
+ L\'extension ne peut pas rester vide
+ Le fichier source %s n\'existe pas
+
+
+ Copier
+ Déplacer
+ Copier / Déplacer
+ Copier vers
+ Déplacer vers
+ Source
+ Destination
+ Sélectionner la destination
+ Cliquez ici pour sélectionner la destination
+ Impossible d\'écrire dans la destination sélectionnée
+ Veuillez choisir une destination
+ La source et la destination ne peuvent pas être identiques
+ Impossible de copier les fichiers
+ Copie…
+ Fichiers copiés avec succès
+ Une erreur est survenue
+ Déplacement…
+ Fichiers déplacés avec succès
+ Certains fichiers n\'ont pu être déplacés
+ Certains fichiers n\'ont pu être copiés
+ Aucun fichier sélectionné
+ Enregistrement…
+ Impossible de créer le dossier %s
+ Impossible de créer le fichier %s
+
+
+ Créer nouveau
+ Dossier
+ Fichier
+ Créer un nouveau dossier
+ Un répertoire ou un fichier avec ce nom existe déjà
+ Le nom contient des caractères invalides
+ Veuillez saisir un nom
+ Une erreur inconnue est survenue.
+
+
+ Le fichier \"%s\" existe déjà
+ Le fichier \"%s\" existe déjà. Ecraser?
+ Le répertoire \"%s\" existe déjà
+ Fusionner
+ Écraser
+ Passer
+ Suffixer avec \'_1\'
+ Appliquer à tous les conflits
+
+
+ Sélectionner un dossier
+ Sélectionner un fichier
+ Confirmer l\'accès au stockage externe
+ Veuillez choisir le dossier racine de la carte SD dans le prochain écran, pour accorder le droit d\'écriture
+ Si vous ne voyez pas la carte SD, essayez ceci
+ Confirmer la sélection
+
+
+ - %d élément
+ - %d éléments
+
+
+
+
+ - %d élément
+ - %d éléments
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Sélectionner le stockage
+ Interne
+ Carte SD
+ Racine
+ Mauvais dossier sélectionné, veuillez sélectionner le dossier racine de votre carte SD
+ Les chemins de la carte SD et du stockage USB OTG ne peuvent être similaires.
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Propriétés
+ Chemin
+ Eléments sélectionnés
+ Compte des enfants directs
+ Compte total des fichiers
+ Résolution
+ Durée
+ Artiste
+ Album
+ Longueur de focale
+ Durée d\'exposition
+ Sensibilité ISO
+ Ouverture
+ Caméra
+ EXIF
+ Titre de la chanson
+
+
+ Couleur d\'arrière-plan
+ Couleur du texte
+ Couleur primaire
+ Couleur de fond
+ Couleur de fond de l\'îcone de l\'application
+ Restaurer les valeurs par défaut
+ Changer couleur
+ Thème
+ Changer une couleur le basculera en thème personnalisé
+ Sauvegarder
+ Rejeter
+ Annuler les changements
+ Etes-vous sûr•e de vouloir annuler vos changements ?
+ Vous avez des changements non sauvegardés. Sauvegarder avant de quitter ?
+ Appliquer ces couleurs à toutes les Applis Simples
+ Couleurs mises à jour. Un nouveau thème nommé \'Partagé\' a été ajouté, veuillez utiliser celui-ci pour mettre à jour les couleurs de toutes les applis à partir de maintenant.
+
+ Un petit Merci pour déverrouiller cette fonctionnalité et soutenir le développeur. Merci!
+ ]]>
+
+
+
+ Lumineux
+ Sombre
+ Solarisé
+ Noir et Rouge
+ Noir & Blanc
+ Personnalisé
+ Partagé
+
+
+ Quoi de neuf ?
+ * seules les mises à jour les plus importantes sont listées ici, elles sont toujours accompagnées de plus petites améliorations qui ne sont pas détaillées
+
+
+ Supprimer
+ Retirer
+ Renommer
+ Partager
+ Partager via
+ Sélectionner tout
+ Masquer
+ Démasquer
+ Masquer dossier
+ Démasquer dossier
+ Afficher temporairement les fichiers masqués
+ Arrêter d\'afficher les medias masqués
+ Vous ne pouvez pas partager autant de contenu à la fois
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Trier par
+ Nom
+ Taille
+ Dernière modification
+ Date prise
+ Titre
+ Nom de fichier
+ Extension
+ Croissant
+ Décroissant
+ Utiliser pour ce dossier uniquement
+
+
+ Êtes-vous sûr•e de vouloir effectuer la suppression ?
+ Êtes-vous sûr•e de vouloir supprimer %s ?
+ Êtes-vous sûr•e de vouloir déplacer %s vers la corbeille ?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Ne pas redemander pour cette session
+ Oui
+ Non
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Saisir PIN
+ Veuillez saisir un PIN
+ PIN erroné
+ Répéter PIN
+ Modèle
+ Insérer modèle
+ Modèle erroné
+ Répéter modèle
+ Empreinte
+ Ajouter une empreinte
+ Veuillez placer votre doigt sur le capteur d\'empreintes digitales
+ L\'identification a échoué
+ Identification bloquée, veuillez réessayer dans quelques instants
+ Vous n\'avez aucune empreinte digitale enregistrée, veuillez en ajouter dans les paramètres de votre appareil
+ Aller aux Paramètres
+ Mot de passe défini avec succès. Veuillez réinstaller l\'appli en cas d\'oubli.
+ Protection mise en place avec succès. Veuillez réinstaller l\'appli en cas d\'oubli.
+
+
+ Yesterday
+ Today
+ Demain
+ secondes
+ minutes
+ heures
+ jours
+
+
+ - %d seconde
+ - %d secondes
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d heure
+ - %d heures
+
+
+ - %d jour
+ - %d jours
+
+
+ - %d semaine
+ - %d semaines
+
+
+ - %d mois
+ - %d mois
+
+
+ - %d an
+ - %d ans
+
+
+
+
+ - %d seconde
+ - %d secondes
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d heure
+ - %d heures
+
+
+
+
+ - %d seconde avant
+ - %d secondes avant
+
+
+ - %d minute avant
+ - %d minutes avant
+
+
+ - %d heure avant
+ - %d heures avant
+
+
+ - %d jour avant
+ - %d jours avant
+
+
+
+
+ - %d seconde
+ - %d secondes
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d heure
+ - %d heures
+
+
+
+ Temps restant jusqu\'au déclenchement de l\'alarme : \n%s
+ Time remaining till the reminder triggers:\n%s
+ Veuillez vérifier que l\'alarme fonctionne correctement avant de lui faire confiance. Elle pourrait ne pas fonctionner correctement, à cause de la politique d\'économie d\'énergie de votre appareil.
+ Veuillez vérifier que les rappels fonctionnent correctement avant de leur faire confiance. Ils pourraient ne pas fonctionner correctement, à cause de la politique d\'économie d\'énergie de votre appareil.
+
+
+ Réveil
+ Répéter
+ Ignorer
+ Pas de rappel
+ Au démarrage
+ Sonneries du système
+ Vos sonneries
+ Ajouter une nouvelle sonnerie
+ Pas de son
+
+
+ Paramètres
+ Purchase Simple Thank You
+ Personnaliser les couleurs
+ Personnaliser les couleurs du widget
+ Utiliser l\'affichage en anglais
+ Ne pas montrer les nouveautés au démarrage
+ Afficher les éléments cachés
+ Taille police
+ Petite
+ Moyenne
+ Large
+ Extra large
+ Visibilité des éléments protégés par un mot de passe
+ Le mot de passe protège l\'ensemble de l\'application
+ Garder la valeur de l\'ancienne modification lors de renommage/déplacement/copie de fichier
+ Afficher une bulle d\'information des éléments qui s\'affichent lorsque vous faites glisser la barre de défilement
+ Ne pas mettre en veille tant que l\'application est active.
+ Ne jamais montrer le dialogue de confirmation de suppression
+ Activer le rafraîchissement en glissant à partir du haut
+ Utiliser le format horaire 24 heures.
+ Démarrer la semaine le dimanche
+ Widgets
+ Toujours utiliser le même intervalle de répétition
+ Temps de répétition
+ Vibrer lors de l\'appui sur un bouton
+ Déplacer les éléments vers la corbeille au lieu de les supprimer
+ Interval de vidage de la corbeille
+ Vider la corbeille
+ Forcer le mode portrait
+
+
+ Visibilité
+ Sécurité
+ Défilement
+ Opérations sur les fichiers
+ Corbeille
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importer…
+ Exporter…
+ Importation réussie
+ Exportation réussie
+ L\'importation a échoué
+ L\'exportation a échoué
+ L\'importation de certains items a échoué
+ L\'exportation de certains items a échoué
+ Aucun item pouvant être exporté n\'a été trouvé
+
+
+ USB OTG
+ Veuillez choisir le répertoire racine du périphérique USB OTG au prochain écran afin de pouvoir y accèder.
+ Mauvais répertoire choisi, merci de sélectionner le répertoire racine du périphérique USB OTG.
+
+
+ Janvier
+ Février
+ Mars
+ Avril
+ Mai
+ Juin
+ Juillet
+ Août
+ Septembre
+ Octobre
+ Novembre
+ Décembre
+
+
+ en Janvier
+ en Février
+ en Mars
+ en Avril
+ en Mai
+ en Juin
+ en Juillet
+ en Août
+ en Septembre
+ en Octobre
+ en Novembre
+ en Décembre
+
+ Lundi
+ Mardi
+ Mercredi
+ Jeudi
+ Vendredi
+ Samedi
+ Dimanche
+
+ L
+ M
+ M
+ J
+ V
+ S
+ D
+
+ Lun
+ Mar
+ Mer
+ Jeu
+ Ven
+ Sam
+ Dim
+
+
+ À propos
+ Pour le code source, visitez
+ Envoyez vos réactions et suggestions à
+ Plus d\'applications
+ Licences tierces
+ Inviter des amis
+ Hey, viens voir %1$s à %2$s
+ Inviter via
+ Nous évaluer
+ Donner
+ Donner
+ Suivez-nous
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Info supplémentaire
+ Version appli : %s
+ OS appareil : %s
+
+
+ nous espérons que vous appréciez cette appli. Elle ne contient aucune publicité, mais vous pouvez soutenir son développement en achetant l\'appli Un petit Merci . Cela nous permettra également de ne plus vous montrer cette boite de dialogue.
+ Merci !
+ ]]>
+
+ Acheter
+ Veuillez mettre à jour Simple Merci à la dernière version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Foire aux questions (FAQ)
+ Avant de poser une question, lisez d\'abord le
+ Pourquoi je ne vois pas les widgets de cette appli dans la liste des widgets ?
+ Probablement parce que l\'appli a été déplacée sur la carte SD. Android a une limitation qui fait qu\'il n\'affiche pas les widgets de cette appli dans ce cas. La seule solution consiste à replacer l\'appli dans le stockage interne via les paramètres du système.
+ Je voudrais vous apporter mon support, mais je ne peux pas vous donner de l\'argent. Existe t-il un autre moyen ?
+ Bien sûr. Vous pouvez recommander ces applis et/ou donner de bonnes évaluations. Vous pouvez aussi nous aider en traduisant les applis dans une nouvelle langue, ou mettre à jour les traductions existantes. Vous pouvez trouver le guide à l\'adresse https://github.com/SimpleMobileTools/General-Discussion , ou juste m\'envoyer un mail à hello@simplemobiletools.com si vous avez besoin d\'aide.
+ J\'ai effacé des fichiers par erreurs. Comment les récupérer ?
+ C\'est malheureusement impossible, car les fichiers sont supprimés instantanément après le dialogue de confirmation et il n\'y a pas de corbeille.
+ Je n\'aime pas les couleurs du widget. Puis-je la changer ?
+ Oui, quand vous glissez le widget sur l\'écran d\'accueil un dialogue de configuration apparaît. Vous verrez des carrés en bas à gauche, en appuyant dessus vous pouvez choisir une nouvelle couleur. Vous pouvez aussi utiliser le curseur pour régler la transparence.
+ Puis-je d\'une manière ou d\'une autre restaurer les fichiers supprimés ?
+ Si ils ont déjà été supprimés, vous ne pouvez pas. Toutefois, vous pouvez activer l\'utilisation de la corbeille à la place de la suppression dans les paramètres de l\'app. Cela devrait seulement y déplacer les fichiers au lieu de les supprimer.
+
+
+ Cette application utilise les bibliothèques tierces suivantes pour me simplifier la vie. Merci.
+ Licences tierces
+ Kotlin (langage de programmation)
+ Subsampling Scale Image View (imageviews zoomables)
+ Glide (chargement et mise en cache d\'image)
+ Picasso (chargement et mise en cache d\'image)
+ Android Image Cropper (découpe et rotation d\'image)
+ RecyclerView MultiSelect (sélection de multiples éléments de liste)
+ RtlViewPager (glissement de droite à gauche)
+ Joda-Time (remplacement de date Java)
+ Stetho (débogage de bases de données)
+ Otto (bus d\'évenement)
+ PhotoView (GIFs zoomables)
+ PatternLockView (protection de modèle)
+ Reprint (protection par empreinte digitale)
+ Gif Drawable (chargement des GIFs)
+ AutoFitTextView (redimensionnement du texte)
+ Robolectric (framework de test)
+ Espresso (assistant de test)
+ Gson (parser JSON)
+ Leak Canary (détecteur de fuite de mémoire)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-gl/strings.xml b/commons/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000..619b64b
--- /dev/null
+++ b/commons/src/main/res/values-gl/strings.xml
@@ -0,0 +1,560 @@
+
+ OK
+ Cancelar
+ Guardar como
+ Archivo guardado con éxito
+ Formato de archivo no válido
+ Error: sin memória
+ Un error ocurrió: %s
+ Abrir con
+ No se encontró una aplicación válida
+ Establecer como
+ Valor copiado en el portapapeles
+ Desconocido
+ Siempre
+ Nunca
+ Detalles
+ Notas
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Favorites
+ Add favorites
+ Add to favorites
+ Remove from favorites
+
+
+ Buscar
+ Escribe por lo menos dos caracteres para iniciar la búsqueda.
+
+
+ Filtrar
+ No se encontraron artículos.
+ Cambiar filtro
+
+
+ Se requiere permiso de almacenamiento
+ Se requiere permiso de Contactos
+ Se requiere permiso de la cámara
+ Se requiere permiso de audio
+
+
+ Renombrar archivo
+ Renombrar carpeta
+ No se pudo renombrar el archivo
+ No se pudo renombrar la carpeta
+ El nombre de la carpeta no puede estar vacío
+ Ya existe una carpeta con ese nombre
+ No se puede cambiar el nombre de la carpeta raíz del almacenamiento
+ Carpeta renombrada correctamente
+ Renombrando carpeta
+ El nombre del archivo no puede estar vacío
+ El nombre del archivo contiene caracteres no válidos
+ El nombre del archivo \'%s\' contiene caracteres no válidos
+ La extensión no puede estar vacía
+ El archivo %s no existe
+
+
+ Copiar
+ Mover
+ Copiar/Mover
+ Copiar a
+ Mover a
+ Origen
+ Destino
+ Seleccionar destino
+ Pulsa aquí para elegir el destino
+ No se pudo escribir en el destino elegido
+ Por favor elige un destino
+ Origen y destino no pueden coincidir
+ No se pudo copiar los archivos
+ Copiando…
+ Archivos copiados correctamente
+ Un error ocurrió
+ Moviendo…
+ Archivos movidos correctamente
+ Algunos archivos no se pudieron mover
+ Algunos archivos no se pudieron copiar
+ No hay archivos seleccionados
+ Guardando…
+ No se puede crear la carpeta %s
+ No se puede crear el fichero %s
+
+
+ Crear nueva
+ Carpeta
+ Archivo
+ Crear nueva carpeta
+ Ya existe un archivo o carpeta con ese nombre
+ El nombre contiene caracteres no válidos
+ Por favor introduce un nombre
+ Ocurrió un error desconocido
+
+
+ El archivo \"%s\" ya existe
+ El archivo \"%s\" ya existe. Sobreescribir?
+ La carpeta \"%s\" ya existe
+ Fusionar
+ Sobreescribir
+ Saltar
+ Añadir \'_1\'
+ Aplicar a todo
+
+
+ Seleccione una carpeta
+ Seleccione un archivo
+ Permita el acceso al almacenamiento externo
+ Por favor, elija la carpeta raíz de la tarjeta SD en la próxima pantalla para conceder el acceso de escritura
+ Si no ve la tarjeta SD, prueba esto
+ Confirmar la selección
+
+
+ - %d elemento
+ - %d elementos
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Seleccione almacenamiento
+ Almacenamiento interno
+ Tarjeta SD
+ Raíz /
+ Se seleccionó la carpeta incorrecta, seleccione la carpeta raíz de su tarjeta SD
+ La targeta SD i el dispositivo OTG no pueden ser los mismos
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Propiedades
+ Ubicación
+ Elementos seleccionados
+ Número de archivos directamente contenidos
+ Número total de archivos
+ Resolución
+ Duración
+ Artista
+ Álbum
+ Longitud focal
+ Tiempo de exposición
+ Velocidad ISO
+ F-number
+ Cámara
+ EXIF
+ Título de la canción
+
+
+ Color del fondo
+ Color del texto
+ Color principal
+ Foreground color
+ App icon color
+ Valores por defecto
+ Change color
+ Tema
+ Cambiar un color hará que cambie a tema personalizado
+ Guardar
+ Deshacer
+ Deshacer cambios
+ ¿Seguro que quiere deshacer los cambios?
+ Tiene cambios sin aplicar. ¿Guardar antes de salir?
+ Aplicar colores a todas las aplicaciones Simple Apps
+ Colores actualizados correctamente. Se ha añadido un nuevo tema llamado \'Shared\', Por favor utilicelo para actualizar los colores de todas las aplicaciones en el futuro.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Claro
+ Oscuro
+ Solarized
+ Rojo Oscuro
+ Blanco & Negro
+ Personalizado
+ Compartido
+
+
+ Qué hay de nuevo
+ * Sólo las actualizaciones más grandes se enumeran aquí, siempre hay algunas pequeñas mejoras también
+
+
+ Eliminar
+ Renombrar
+ Compartir
+ Compartir con
+ Seleccionar todo
+ Ocultar
+ Montrar
+ Ocultar carpeta
+ Mostrar carpeta
+ Mostrar ocultos temporalmente
+ No mostrar ocultos
+ No puedes compartir todo este contenido a la vez
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Ordenar por
+ Nombre
+ Tamaño
+ Modificación
+ Creación
+ Título
+ Nombre del archivo
+ Extensión
+ Ascendente
+ Descendente
+ Sólo para esta carpeta
+
+
+ ¿Está seguro de querer continuar con la eliminación?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Non preguntar de novo en esta sesión
+ Sí
+ No
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Introduzca PIN
+ Por favor introduzca PIN
+ PIN erroneo
+ Repetir PIN
+ Patrón
+ Inserte Patrón
+ Patrón erroneo
+ Repetir Patrón
+ Huella dactilar
+ Añadir Huella dactilar
+ Por favor pon el dedo en el lector de Huella dactilares
+ Autenticación fallida
+ Autenticación bloqueada, por favor intente de nuevo en un momento
+ No tiene huellas dactilares registradas, por favor agregue algunas en la Configuración de su dispositivo
+ Ir a la configuración
+ Se ha agregado la contraseña exitosamente. Por favor, vuelva a instalar la aplicación en caso de que la olvide.
+ Configuración de protección con éxito. Por favor, vuelva a instalar la aplicación en caso de problemas al restablecerla.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minutos
+ horas
+ días
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+ - %d día
+ - %d días
+
+
+ - %d semana
+ - %d semanas
+
+
+ - %d mes
+ - %d meses
+
+
+ - %d ano
+ - %d anos
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d minuto antes
+ - %d minutos antes
+
+
+ - %d hora antes
+ - %d horas antes
+
+
+ - %d día antes
+ - %d días antes
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Retrasar
+ Dismiss
+ Sin recordatorio
+ Ao inicio
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Ajustes
+ Purchase Simple Thank You
+ Cambiar colores
+ Customize widget colors
+ Usar el idioma Inglés
+ Permitir mostrar novedades al iniciar
+ Mostrar elementos ocultos
+ Tamaño de fuente
+ Pequeña
+ Mediana
+ Grande
+ Enorme
+ Contraseña para proteger los medios ocultos
+ Contraseña para proteger toda la aplicación
+ Mantener el valor de ultima modificación al copiar/mover/renombrar los archivos
+ Mostrar una burbuja de información al desplazar elementos arrastrando la barra de desplazamiento
+ Prevent phone from sleeping while the app is in foreground
+ Omitir siempre el diálogo de confirmación de eliminación
+ Habilitar tirar desde la parte superior para refrescar
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibilidad
+ Seguridad
+ Desplazamiento
+ Operaciones de ficheros
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importando…
+ Exportando…
+ Importación correcta
+ Exportación correcta
+ Importación fallida
+ Exportación fallida
+ La importación de algunas entradas ha fallado
+ La exportación de algunas entradas ha fallado
+ No se han encontrado entradas para exportar
+
+
+ OTG
+ Elija la carpeta raíz del dispositivo OTG en la pantalla siguiente, para otorgar acceso
+ Carpeta incorrecta, seleccione la carpeta raíz de su dispositivo OTG
+
+
+ Xaneiro
+ Febreiro
+ Marzo
+ Abril
+ Maio
+ Xuño
+ Xullo
+ Agosto
+ Setembro
+ Outubro
+ Novembro
+ Nadal
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Segunda
+ Terceira
+ Cuarta
+ Quinta
+ Sexta
+ Sábado
+ Domingo
+
+ S
+ T
+ C
+ Q
+ S
+ S
+ D
+
+ Seg
+ Ter
+ Cua
+ Qui
+ Sex
+ Sáb
+ Dom
+
+
+ Acerca de
+ Más aplicaciones sencillas y código fuente en
+ Envía tus comentarios o sugerencias a
+ Más aplicaciones
+ Licencias de terceros
+ Invitar a amigos
+ Hola, ven y mira %1$s en %2$s
+ Invitar con
+ Vótanos en Play Store
+ Donar
+ Donar
+ Síguenos
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Información adicional
+ App version: %s
+ Device OS: %s
+
+
+ Espero que estés disfrutando de la aplicación. No contiene anuncios, por lo que debe respaldar su desarrollo comprando la aplicación Simple Thank You , también evitará que este diálogo vuelva a aparecer.
+ Gracias!
+ ]]>
+
+ Comprar
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ solo te informamos que recientemente se ha lanzado una nueva aplicación:
+ %2$s
+ Puedes descargarla presionando el título.
+ Gracias
+ ]]>
+
+
+
+ Preguntas frecuentes
+ Before you ask a question, please first read the
+ ¿Cómo es que no veo este widget de aplicaciones en la lista de widgets?
+ Lo más probable es que haya movido la aplicación en una tarjeta SD. Hay una limitación del sistema Android que oculta los widgets de la aplicación dada
+ en ese caso. La única solución es volver a colocar la aplicación en el almacenamiento interno a través de la configuración de su dispositivo.
+ Quiero apoyarte, pero no puedo donar dinero. ¿Hay algo mas que pueda hacer?
+ Sí, por supuesto. Puede correr la voz sobre las aplicaciones o dar buenos comentarios y valoraciones. También puede ayudar traduciendo las aplicaciones en un nuevo idioma o simplemente actualizar algunas traducciones existentes.
+ Puede encontrar la guía en https://github.com/SimpleMobileTools/General-Discussion, o simplemente envíeme un mensaje a hello@simplemobiletools.com si necesita ayuda.
+ Borré algunos archivos por error, ¿cómo puedo recuperarlos?
+ Tristemente, no puedes. Los archivos se eliminan instantáneamente después del diálogo de confirmación, no hay papelera disponible.
+ No me gustan los colores del widget, ¿puedo cambiarlos?
+ Sí, al arrastrar un widget en la pantalla de inicio aparece una pantalla de configuración de widgets. Verá cuadrados de colores en la esquina inferior izquierda, solo presione para elegir un nuevo color. Puede usar el control deslizante para ajustar el alfa también.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ Esta aplicación usa las siguientes bibliotecas de terceros que hacen mi vida más fácil. Gracias.
+ Licencias de terceros
+ Kotlin (Programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-hi-rIN/strings.xml b/commons/src/main/res/values-hi-rIN/strings.xml
new file mode 100644
index 0000000..bc78a59
--- /dev/null
+++ b/commons/src/main/res/values-hi-rIN/strings.xml
@@ -0,0 +1,561 @@
+
+ ठीक
+ रद्द करें
+ Save as
+ File saved successfully
+ Invalid file format
+ Out of memory error
+ An error occurred: %s
+ Open with
+ No valid app found
+ Set as
+ Value copied to clipboard
+ Unknown
+ Always
+ Never
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Favorites
+ Add favorites
+ Add to favorites
+ Remove from favorites
+
+
+ Search
+ Type in at least 2 characters to start the search.
+
+
+ Filter
+ No items found.
+ Change filter
+
+
+ Storage permission is required
+ Contacts permission is required
+ Camera permission is required
+ Audio permission is required
+
+
+ Rename file
+ Rename folder
+ Could not rename the file
+ Could not rename the folder
+ Folder name must not be empty
+ A folder with that name already exists
+ Cannot rename the root folder of a storage
+ Folder renamed successfully
+ Renaming folder
+ Filename cannot be empty
+ Filename contains invalid characters
+ Filename \'%s\' contains invalid characters
+ Extension cannot be empty
+ Source file %s doesn\'t exist
+
+
+ Copy
+ Move
+ Copy / Move
+ Copy to
+ Move to
+ Source
+ Destination
+ Select destination
+ Click here to select destination
+ Could not write to the selected destination
+ Please select a destination
+ Source and destination cannot be the same
+ Could not copy the files
+ Copying…
+ Files copied successfully
+ An error occurred
+ Moving…
+ Files moved successfully
+ Some files could not be moved
+ Some files could not be copied
+ No files selected
+ Saving…
+ Could not create folder %s
+ Could not create file %s
+
+
+ Create new
+ Folder
+ File
+ Create new folder
+ A file or folder with that name already exists
+ The name contains invalid characters
+ Please enter a name
+ An unknown error occurred
+
+
+ File \"%s\" already exists
+ File \"%s\" already exists. Overwrite?
+ Folder \"%s\" already exists
+ Merge
+ Overwrite
+ Skip
+ Append with \'_1\'
+ Apply to all
+
+
+ Select a folder
+ Select a file
+ Confirm external storage access
+ Please choose the root folder of the SD card on the next screen, to grant write access
+ If you don\'t see the SD card, try this
+ Confirm selection
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Select storage
+ Internal
+ SD Card
+ Root
+ Wrong folder selected, please select the root folder of your SD card
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Properties
+ Path
+ Items selected
+ Direct children count
+ Total files count
+ Resolution
+ Duration
+ Artist
+ Album
+ Focal length
+ Exposure time
+ ISO speed
+ F-number
+ Camera
+ EXIF
+ Song title
+
+
+ Background color
+ Text color
+ Primary color
+ Foreground color
+ App icon color
+ Restore defaults
+ Change color
+ Theme
+ Changing a color will make it switch to Custom theme
+ Save
+ Discard
+ Undo changes
+ Are you sure you want to undo your changes?
+ You have unsaved changes. Save before exit?
+ Apply colors to all Simple Apps
+ Colors updated successfully. A new Theme called \'Shared\' has been added, please use that for updating all app colors in the future.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Light
+ Dark
+ Solarized
+ Dark red
+ Black & White
+ Custom
+ Shared
+
+
+ What\'s new
+ * only the bigger updates are listed here, there are always some smaller improvements too
+
+
+ डिलीट
+ Remove
+ Rename
+ Share
+ Share via
+ Select all
+ Hide
+ Unhide
+ Hide folder
+ Unhide folder
+ Temporarily show hidden
+ Stop showing hidden media
+ You cannot share this much content at once
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sort by
+ Name
+ Size
+ Last modified
+ Date taken
+ Title
+ Filename
+ Extension
+ Ascending
+ Descending
+ Use for this folder only
+
+
+ Are you sure you want to proceed with the deletion?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Do not ask again in this session
+ Yes
+ No
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Enter PIN
+ Please enter a PIN
+ Wrong PIN
+ Repeat PIN
+ Pattern
+ Insert pattern
+ Wrong pattern
+ Repeat pattern
+ Fingerprint
+ Add fingerprint
+ Please place your finger on the fingerprint sensor
+ Authentication failed
+ Authentication blocked, please try again in a moment
+ You have no fingerprints registered, please add some in the Settings of your device
+ Go to Settings
+ Password setup successfully. Please reinstall the app in case you forget it.
+ Protection setup successfully. Please reinstall the app in case of problems with reseting it.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minutes
+ hours
+ days
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+ - %d day
+ - %d days
+
+
+ - %d week
+ - %d weeks
+
+
+ - %d month
+ - %d months
+
+
+ - %d year
+ - %d years
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d minute before
+ - %d minutes before
+
+
+ - %d hour before
+ - %d hours before
+
+
+ - %d day before
+ - %d days before
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ No reminder
+ शुरुआत के समय
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Settings
+ Purchase Simple Thank You
+ Customize colors
+ Customize widget colors
+ Use English language
+ Avoid showing What\'s New on startup
+ Show hidden items
+ Font size
+ Small
+ Medium
+ Large
+ Extra large
+ Password protect hidden item visibility
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ File operations
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importing…
+ Exporting…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ जनवरी
+ फरवरी
+ मार्च
+ अप्रैल
+ मई
+ जून
+ जुलाई
+ अगस्त
+ सितंबर
+ अक्टूबर
+ नवंबर
+ दिसंबर
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ Saturday
+ Sunday
+
+ सो
+ मं
+ बू
+ गु
+ शु
+ श
+ र
+
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+
+
+ के बारे में
+ अधिक एप्पस व सोर्स कोड
+ अपनी राय या सुझाव को भेजें
+ More apps
+ तीसरी पार्टी के लाइसेंस
+ मित्रो को आमन्त्रित करें
+ अरे, %1$s को %2$s पर देखो
+ के माध्यम से आमंत्रित
+ हमे प्ले स्टोर पर रेट करें
+ Donate
+ Donate
+ हमे फॉलो करें
+ सं %1$s\nकॉपीराइट © सिंपल मोबाइल टूल्स %2$d
+ Additional info
+ App version: %s
+ Device OS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ मेरा जीवन आसान करने के लिए यह एप्प तीसरे पक्ष की लाइब्रेरी का इस्तेमाल करती हैं। धन्यवाद।
+ तीसरे पक्ष के लाइसेंस
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ जोड़ा-टाइम(जावा डेट रिप्लेसमेंट)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-hr/strings.xml b/commons/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..53d8453
--- /dev/null
+++ b/commons/src/main/res/values-hr/strings.xml
@@ -0,0 +1,581 @@
+
+ U redu
+ Poništi
+ Spremi kao
+ Datoteka uspješno spremljena
+ Nevažeći format datoteke
+ Greška, nema slobodnog prostora
+ Greška se dogodila: %s
+ Otvori s
+ Nema odgovarajuće aplikacije
+ Postavi kao
+ Vrijednost spremljena u međuspremnik
+ Unknown
+ Always
+ Never
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Favoriti
+ Dodaj favorite
+ Dodaj u favorite
+ Ukloni iz favorita
+
+
+ Traži
+ Type in at least 2 characters to start the search.
+
+
+ Filter
+ No items found.
+ Change filter
+
+
+ Potrebna je dozvola za prostor za pohranu
+ Potrebna je dozvola za kontakte
+ Potrebna je dozvola za kameru
+ Potrebna je dozvola za zvuk
+
+
+ Preimenuj datoteku
+ Preimenuj direktorij
+ Nije moguće preimenovati datoteku
+ Nije moguće preimenovati direktorij
+ Naziv direktorija ne smije biti prazan
+ Direktorij s tim imenom već postoji
+ Nije moguće preimenovati root direktorij prostora za pohranu
+ Direktorij uspješno preimenovan
+ Preimenovanje direktorija
+ Naziv datoteke ne smije biti prazan
+ Naziv datoteke sadrži ne podržane znakove
+ Filename \'%s\' contains invalid characters
+ Ekstenzija ne smije biti prazna
+ Source file %s doesn\'t exist
+
+
+ Kopiraj
+ Premjesti
+ Kopiraj / Premjesti
+ Kopiraj u
+ Premjesti u
+ Izvor
+ Odredište
+ Odabir odredišta
+ Kliknite ovdje za odabir odredišta
+ Nije moguće zapisivanje na odabranom odredištu
+ Molimo odaberite odredište
+ Izvor i odredište ne mogu biti isti
+ Nije moguće kopiranje datoteka
+ Kopiranje…
+ Datoteke uspješno kopirane
+ Greška se dogodila
+ Premještanje…
+ Datoteke uspješno premještene
+ Neke datoteke nije bilo moguće premjestiti
+ Neke datoteke nije bilo moguće kopirati
+ Nema odabranih datoteka
+ Spremanje…
+ Nije moguće stvoriti direktorij %s
+ Nije moguće stvoriti datoteku %s
+
+
+ Stvori novo
+ Direktorij
+ Datoteka
+ Stvori novi direktorij
+ Datoteka ili direktorij s tim nazivom već postoje
+ Naziv sadrži ne važeće znakove
+ Molimo unesite naziv
+ Nepoznata greška se dogodila
+
+
+ Datoteka \"%s\" već postoji
+ Datoteka \"%s\" već postoji. Presnimiti?
+ Folder \"%s\" already exists
+ Merge
+ Presnimi
+ Preskoči
+ Dodaj s \'_1\'
+ Primjeni na sve sukobe
+
+
+ Odaberite direktorij
+ Odaberite datoteku
+ Potvrdite pristup vanjskom prostoru za pohranu
+ Molimo odaberite root direktorij SD kartice na sljedećem prozoru, da bi ste omogućili zapisivanje
+ Ukoliko ne vidite SD karticu, probajte ovo
+ Potvrdi odabir
+
+
+ - %d stavka
+ - %d stavke
+ - %d stavki
+
+
+
+
+ - %d item
+ - %d items
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+ - Deleting %d items
+
+
+
+ Odaberite prostor za pohranu
+ Unutranji prostor za pohranu
+ SD kartica
+ Root
+ Odabran je pogrešan direktorij, molimo odaberite root direktorij Vaše SD kartice
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Svojstva
+ Putanja
+ Odabrane stavke
+ Zbroj podstavki
+ Ukupni zbroj stavki
+ Rezolucija
+ Trajanje
+ Umjetnik
+ Album
+ Žarišna duljina
+ Vrijeme izloženosti
+ ISO brzina
+ F-broj
+ Kamera
+ EXIF
+ Song title
+
+
+ Boja pozadine
+ Boja teksta
+ Primarna boja
+ Foreground color
+ App icon color
+ Vrati zadano
+ Change color
+ Tema
+ Promjenom boje, prelazi se na Prilagođenu temu
+ Spremi
+ Odbaci
+ Poništi promjene
+ Jeste li sigurni da želite poništiti Vaše promjene?
+ Imate ne spremljene promjene. Želite li spremiti prije izlaska?
+ Primjeni boje na sve Simple Aplikacije
+ Uspješno ažurirane boje. Nova tema pod nazivom \'Shared\' je dodana, molimo koristite ju za ažuriranje svih boja u aplikaciji u budućnosti.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Svijetla
+ Tamna
+ Solarizirana
+ Tamno crvena
+ Black & White
+ Prilagođena
+ Dijeljena
+
+
+ Što je novo
+ * samo velika ažuriranja su ovdje izlistana, ali imala ažuriranja su također prisutna
+
+
+ Izbriši
+ Remove
+ Preimenuj
+ Dijeli
+ Dijeli preko
+ Odaberi sve
+ Sakrij
+ Otkrij
+ Sakrij direktorij
+ Otkrij direktorij
+ Privremeno prikaži skriveno
+ Zaustavi prikazivanje skrivenih stavki
+ Nije moguće dijeliti ovoliko stavki odjednom
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sortiraj po
+ Naziv
+ Veličina
+ Datum zadnje izmjenjene
+ Datum stvaranja
+ Naslov
+ Naziv datoteke
+ Ekstenzija
+ Uzlazno
+ Silazno
+ Koristi samo za ovaj direktorij
+
+
+ Jeste li sigurni da želite nastaviti s brisanjem?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Ne pitaj me više u ovoj sesiji
+ Da
+ Ne
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Unesite PIN
+ Molimo unesite PIN
+ Pogrešan PIN
+ Ponovite PIN
+ Uzorak
+ Unesite uzorak
+ Krivi uzorak
+ Ponovite uzorak
+ Otisak prsta
+ Dodajte otisak prsta
+ Molimo postavite prst na senzor otiska prsta
+ Neuspješna autentifikacija
+ Blokirana autentifikacija, molimo pokušajte za par trenutaka
+ Nemate registiranih otisaka prstiju, molimo dodajte ih u postavkama vašeg uređaja
+ Idi u Postavke
+ Uspješno postavljanje lozinke. Molimo reinstalirajte aplikaciju u slučaju da je zaboravite.
+ Uspješno postavljanje zaštite. Molimo reinstalirajte aplikaciju u slučaju problema s resetiranjem.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minutes
+ hours
+ days
+
+
+ - %d sekunda
+ - %d sekunde
+ - %d sekundi
+
+
+ - %d minuta
+ - %d minute
+ - %d minuti
+
+
+ - %d sat
+ - %d sata
+ - %d sati
+
+
+ - %d day
+ - %d days
+ - %d days
+
+
+ - %d week
+ - %d weeks
+ - %d weeks
+
+
+ - %d month
+ - %d months
+ - %d months
+
+
+ - %d year
+ - %d years
+ - %d years
+
+
+
+
+ - %d second
+ - %d seconds
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+ - %d seconds before
+
+
+ - %d minute before
+ - %d minutes before
+ - %d minutes before
+
+
+ - %d hour before
+ - %d hours before
+ - %d hours before
+
+
+ - %d day before
+ - %d days before
+ - %d days before
+
+
+
+
+ - %d second
+ - %d seconds
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+ - %d hours
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ No reminder
+ At start
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Postavke
+ Purchase Simple Thank You
+ Prilagodi boje
+ Customize widget colors
+ Koristi Engleski jezik
+ Avoid showing What\'s New on startup
+ Show hidden items
+ Veličina fonta
+ Malo
+ Srednje
+ Veće
+ Puno veće
+ Vidljivost skrivenih, lozinkom zaštićenih stavki
+ Zaštiti cijelu aplikaciju lozinkom
+ Zadrži stari datum zadnje izmjene pri kopiranje/premještanju/preimenovanju datoteke
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ File operations
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importing…
+ Exporting…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ January
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ Saturday
+ Sunday
+
+ M
+ T
+ W
+ T
+ F
+ S
+ S
+
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+
+
+ O nama
+ Za pregled izvornog koda posjetite
+ Pošaljite nam povratne informacije ili prijedloge na
+ Više aplikacija
+ Licence trećih strana
+ Pozovite prijatelje
+ Hej, posjeti nas na %1$s u %2$s
+ Pozovite preko
+ Ocijenite nas
+ Donirajte
+ Donirajte
+ Pratite nas
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Dodatne informacije
+ Verzija aplikacije: %s
+ OS uređaja: %s
+
+
+ nadam se da uživate u aplikaciji. Ne sadrži reklame, stoga nas podržite njen razvoj kupovinom Simple Thank You aplikacije, što će spriječiti da se ovaj prozor ponovno pojavljuje.
+ Hvala Vam!
+ ]]>
+
+ Kupi
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ Ova aplikacija koristi biblioteke trećih strana kako bi olakšala moj život. Hvala Vam.
+ Licence trećih strana
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-hu/strings.xml b/commons/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..ddca80a
--- /dev/null
+++ b/commons/src/main/res/values-hu/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Cancel
+ Save as
+ File saved successfully
+ Invalid file format
+ Out of memory error
+ An error occurred: %s
+ Open with
+ No valid app found
+ Set as
+ Value copied to clipboard
+ Unknown
+ Always
+ Never
+ Details
+ Jegyzetek
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Kedvencek
+ Add favorites
+ Kedvencnek jelölés
+ Törlés a kedvencek közül
+
+
+ Search
+ Type in at least 2 characters to start the search.
+
+
+ Filter
+ No items found.
+ Change filter
+
+
+ Storage permission is required
+ Contacts permission is required
+ Camera permission is required
+ Audio permission is required
+
+
+ Rename file
+ Rename folder
+ Could not rename the file
+ Could not rename the folder
+ Folder name must not be empty
+ A folder with that name already exists
+ Cannot rename the root folder of a storage
+ Folder renamed successfully
+ Renaming folder
+ Filename cannot be empty
+ Filename contains invalid characters
+ Filename \'%s\' contains invalid characters
+ Extension cannot be empty
+ Source file %s doesn\'t exist
+
+
+ Copy
+ Move
+ Copy / Move
+ Copy to
+ Move to
+ Source
+ Destination
+ Select destination
+ Click here to select destination
+ Could not write to the selected destination
+ Please select a destination
+ Source and destination cannot be the same
+ Could not copy the files
+ Copying…
+ Files copied successfully
+ An error occurred
+ Moving…
+ Files moved successfully
+ Some files could not be moved
+ Some files could not be copied
+ No files selected
+ Saving…
+ Could not create folder %s
+ Could not create file %s
+
+
+ Create new
+ Folder
+ File
+ Create new folder
+ A file or folder with that name already exists
+ The name contains invalid characters
+ Please enter a name
+ An unknown error occurred
+
+
+ File \"%s\" already exists
+ File \"%s\" already exists. Overwrite?
+ Folder \"%s\" already exists
+ Merge
+ Overwrite
+ Skip
+ Append with \'_1\'
+ Apply to all
+
+
+ Select a folder
+ Select a file
+ Confirm external storage access
+ Please choose the root folder of the SD card on the next screen, to grant write access
+ If you don\'t see the SD card, try this
+ Confirm selection
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Select storage
+ Internal
+ SD Card
+ Root
+ Wrong folder selected, please select the root folder of your SD card
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Properties
+ Path
+ Items selected
+ Direct children count
+ Total files count
+ Resolution
+ Duration
+ Artist
+ Album
+ Focal length
+ Exposure time
+ ISO speed
+ F-number
+ Camera
+ EXIF
+ Song title
+
+
+ Background color
+ Text color
+ Primary color
+ Foreground color
+ App icon color
+ Restore defaults
+ Change color
+ Theme
+ Changing a color will make it switch to Custom theme
+ Save
+ Discard
+ Undo changes
+ Are you sure you want to undo your changes?
+ You have unsaved changes. Save before exit?
+ Apply colors to all Simple Apps
+ Colors updated successfully. A new Theme called \'Shared\' has been added, please use that for updating all app colors in the future.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Light
+ Dark
+ Solarized
+ Dark red
+ Black & White
+ Egyedi
+ Shared
+
+
+ What\'s new
+ * only the bigger updates are listed here, there are always some smaller improvements too
+
+
+ Törlés
+ Remove
+ Rename
+ Share
+ Share via
+ Select all
+ Hide
+ Unhide
+ Hide folder
+ Unhide folder
+ Temporarily show hidden
+ Stop showing hidden media
+ You cannot share this much content at once
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sort by
+ Name
+ Size
+ Last modified
+ Date taken
+ Title
+ Filename
+ Extension
+ Ascending
+ Descending
+ Use for this folder only
+
+
+ Are you sure you want to proceed with the deletion?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Do not ask again in this session
+ Yes
+ No
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Enter PIN
+ Please enter a PIN
+ Wrong PIN
+ Repeat PIN
+ Pattern
+ Insert pattern
+ Wrong pattern
+ Repeat pattern
+ Fingerprint
+ Add fingerprint
+ Please place your finger on the fingerprint sensor
+ Authentication failed
+ Authentication blocked, please try again in a moment
+ You have no fingerprints registered, please add some in the Settings of your device
+ Go to Settings
+ Password setup successfully. Please reinstall the app in case you forget it.
+ Protection setup successfully. Please reinstall the app in case of problems with reseting it.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ perc
+ óra
+ nap
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+ - %d day
+ - %d days
+
+
+ - %d week
+ - %d weeks
+
+
+ - %d month
+ - %d months
+
+
+ - %d year
+ - %d years
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d percel korábban
+ - %d percel korábban
+
+
+ - %d órával korábban
+ - %d órával korábban
+
+
+ - %d nappal korábban
+ - %d nappal korábban
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ No reminder
+ A kezdetekor
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Settings
+ Purchase Simple Thank You
+ Customize colors
+ Customize widget colors
+ Use English language
+ Avoid showing What\'s New on startup
+ Show hidden items
+ Betűméret
+ Kicsi
+ Közepes
+ Nagy
+ Óriási
+ Password protect hidden item visibility
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ File operations
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importing…
+ Exporting…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ Január
+ Február
+ Március
+ Április
+ Május
+ Június
+ Július
+ Augusztus
+ Szeptember
+ Október
+ November
+ December
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ Saturday
+ Sunday
+
+ H
+ K
+ SZ
+ CS
+ P
+ SZ
+ V
+
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+
+
+ Információ
+ További egyszerű alkalmazások és a forráskód itt
+ Visszajelzés és javaslatok
+ More apps
+ Harmadik fél licenszek
+ Barátok meghívása
+ Szia, ezt nézd meg! %1$s itt: %2$s
+ Meghívó küldése itt
+ Értékelés
+ Donate
+ Donate
+ Követés
+ %1$s verzió\nSzerzői jogok: © Simple Mobile Tools %2$d
+ Additional info
+ App version: %s
+ Device OS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ Ez az alkalmazás az alábbi könyvtárakat használja, hogy a munkám egyszerűbb legyen. Köszönöm szépen.
+ Harmadik fél licenszek
+ Kotlin (programozási nyelv)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java dátum helyettesítérére)
+ Stetho (adatbázisok hibakeresésére)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-id/strings.xml b/commons/src/main/res/values-id/strings.xml
new file mode 100644
index 0000000..61fc80c
--- /dev/null
+++ b/commons/src/main/res/values-id/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Batal
+ Simpan Sebagai
+ File Sukses Tersimpan
+ Format File Invalid
+ Error Out of Memori
+ Terjadi Kesalahan: %s
+ Buka dengan
+ Tidak Ada Aplikasi
+ Setel Sebagai
+ Di copy ke Clipboard
+ Tidak di ketahui
+ Selalu
+ Tidak Pernah
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Favorit
+ Tambahkan Favorit
+ Tambahkan Ke Favorit
+ Hapus Dari Favorit
+
+
+ Cari
+ Ketik minimal 2 Karakter Untuk Mencari.
+
+
+ Saring
+ Tidak Ditemukan.
+ Tukar Penyaring
+
+
+ Memerlukan Izin Akses Penyimpanan
+ Memerlukan Izin Akses Kontak
+ Memerlukan Izin Akses Kamera
+ Memerlukan Izin Akses Audio
+
+
+ Ganti Nama File
+ Ganti Nama Folder
+ Tidak Bisa Mengganti Nama File
+ Tidak Bisa Mengganti Nama Folder
+ Folder Nama Tidak Boleh Kosong
+ Folder Dengan Nama Ini Sudah Ada
+ Folder Root Penyimpanan Tidak Dapat Di Ubah
+ Penggantian Nama Folder Berhasil
+ Mengganti Nama Folder
+ Nama File Tidak Boleh Kosong
+ Nama mengandung Karakter Tidak Di Dukung
+ Filename \'%s\' contains invalid characters
+ Extension Tidak Boleh Kosong
+ Source file %s doesn\'t exist
+
+
+ Salin
+ Pindah
+ Salin / Pindah
+ Salin Ke
+ Pindah Ke
+ Sumber
+ Destinasi
+ Pilih Destinasi
+ Klik Di Sini Untuk Memilih Destinasi
+ Tidak Dapat Menulis Destinasi Yang Di Pilih
+ Mohon Pilih Destinasi
+ Sumber Dan Destinasi Tidak Boleh Sama
+ Tidak Dapat Menyalin File
+ Menyalin…
+ File Berhasil Di Salin
+ Terjadi Kesalahan
+ Memindahkan…
+ File Berhasil Di Pindahkan
+ Beberapa File Tidak Dapat Di pindah
+ Beberapa File Tidak Dapat Di Salin
+ Tidak Ada File Yang Di Pilih
+ Menyimpan…
+ Tidak Dapat Membuat Folder %s
+ Tidak Dapat Membuat File %s
+
+
+ Buat Baru
+ Folder
+ File
+ Buat Folder Baru
+ File Atau Folder Dengan Nama Ini Sudah Ada
+ Nama Mengandung Karakter Tidak Di dukung
+ Masukkan Nama
+ Terjadi Kesalahan Tidak Di Ketahui
+
+
+ File \"%s\" Sudah Ada
+ File \"%s\" Sudah Ada. Timpa saja?
+ Folder \"%s\" Sudah Ada
+ Gabung
+ Timpa
+ Lewati
+ Tambahkan Dengan \'_1\'
+ Terapkan Ke Semua
+
+
+ Pilih Folder
+ Pilih File
+ Konfirmasi Penyimpanan Eksternal
+ Pilih Folder Root SD Card Untuk Layar Berikutnya,Agar Memberi Akses Tulis
+ Jika Tidak Menemukan SD card, Coba Ini
+ Konfirmasi
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Pilih Penyimpanan
+ Internal
+ SD Card
+ Root
+ Anda Memilih Folder Yang Salah
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Properties
+ Path
+ Item Terpilih
+ Direct children count
+ Total files count
+ Resolusi
+ Durasi
+ Artist
+ Album
+ Focal length
+ Exposure time
+ ISO speed
+ F-number
+ Camera
+ EXIF
+ Judul lagu
+
+
+ Warna Latar
+ Warna Teks
+ Warna Utama
+ Foreground color
+ App icon color
+ Kembalikan Ke Awal
+ Change color
+ Tema
+ Mengubah Warna Akan Membuat Beralih Ke Tema Kustom
+ Simpan
+ Buang
+ Batalkan Perubahan
+ Apa Kamu Yakin?
+ Kamu Punya Perubahan Yang Belum Di simpan,Simpan?
+ Terapkan Warna Untuk Semua Simple apps
+ Pembaharuan Warna Berhasil. Tema Baru Di Sebut \'Shared\' Telah Di Tambahkan, Mohon Pakai Itu Untuk Pembaharuan Semua Warna Di Masa Akan Datang.
+
+ Terimakasih Untuk Unlock Dan Memberi Dukungan,Terima Kasih!
+ ]]>
+
+
+
+ Light
+ Dark
+ Solarized
+ Dark red
+ Black & White
+ Custom
+ Shared
+
+
+ What\'s new
+ * only the bigger updates are listed here, there are always some smaller improvements too
+
+
+ Hapus
+ Remove
+ Ganti Nama
+ Share
+ Share via
+ Pilih Semua
+ Sembunyikan
+ Munculkan
+ Sembunyikan folder
+ Munculkan folder
+ Temporarily show hidden
+ Stop showing hidden media
+ You cannot share this much content at once
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sort by
+ Nama
+ Ukuran
+ Last modified
+ Date taken
+ Title
+ Filename
+ Extension
+ Ascending
+ Descending
+ Pakai Hanya Untuk Folder Ini
+
+
+ Are you sure you want to proceed with the deletion?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Do not ask again in this session
+ Ya
+ Tidak
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Enter PIN
+ Please enter a PIN
+ Wrong PIN
+ Repeat PIN
+ Pattern
+ Insert pattern
+ Wrong pattern
+ Repeat pattern
+ Fingerprint
+ Tambahkan fingerprint
+ Mohon Tempatkan Jari mu Di Fingerprint Sensor
+ Authentication failed
+ Autentikasi di blok,Mohon Coba Kembali
+ Kamu Tidak Punya Fingerprint Yang Terdaftar, Mohon Tambahkan Di Pengaturan
+ Ke Pengaturan
+ Pengaturan Password Berhasil. Install Ulang Apabila Terlupa.
+ Pengaturan Proteksi Berhasil. Install Ulang Aplikasi Jika Terjadi Masalah Dengan Reset.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minutes
+ hours
+ days
+
+
+ - %d Detik
+ - %d Detik
+
+
+ - %d menit
+ - %d menit
+
+
+ - %d Jam
+ - %d Jam
+
+
+ - %d day
+ - %d days
+
+
+ - %d week
+ - %d weeks
+
+
+ - %d month
+ - %d months
+
+
+ - %d year
+ - %d years
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d minute before
+ - %d minutes before
+
+
+ - %d hour before
+ - %d hours before
+
+
+ - %d day before
+ - %d days before
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ No reminder
+ At start
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Pengaturan
+ Purchase Simple Thank You
+ Sesuaikan Warna
+ Customize widget colors
+ Gunakan Bahasa Inggris
+ Avoid showing What\'s New on startup
+ Show hidden items
+ Font size
+ Small
+ Medium
+ Large
+ Extra large
+ Password protect hidden item visibility
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Keamanan
+ Scrolling
+ File operasi
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importing…
+ Exporting…
+ Importing Berhasil
+ Exporting Berhasil
+ Importing Gagal
+ Exporting Gagal
+ Importing Beberapa Entri Gagal
+ Exporting Beberapa Entri Gagal
+ Tidak Ada Entri Untuk Exporting Di Temukan
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ January
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ Saturday
+ Sunday
+
+ M
+ T
+ W
+ T
+ F
+ S
+ S
+
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+
+
+ Tentang
+ Untuk Sumber Kode Kunjungi
+ Kirimkan Masukan Dan Bantuan
+ More apps
+ Lisensi pihak Ke Tiga
+ Invite friends
+ Hey, Mari check out %1$s at %2$s
+ Invite via
+ Rate us
+ Donasi
+ Donasi
+ Follow us
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Tambahan info
+ App version: %s
+ Device OS: %s
+
+
+ Harap Menikmati Aplikasi Ini. Ini Tidak Ada Iklan,Mohon Beri Dukungan Pengembangan Dengan PurchaseTerimakasih app, it will also prevent this dialog from showing up again.
+ Terimakasih!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ This app uses the following third party libraries to make my life easier. Thank you.
+ Third party licences
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-it/strings.xml b/commons/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..2ad0680
--- /dev/null
+++ b/commons/src/main/res/values-it/strings.xml
@@ -0,0 +1,562 @@
+
+ OK
+ Annulla
+ Salva come
+ File salvato correttamente
+ Formato file non valido
+ Errore memoria esaurita
+ Si è verificato un errore: %s
+ Apri con
+ Nessun app valida trovata
+ Imposta come
+ Valore copiato negli appunti
+ Sconosciuto
+ Sempre
+ Mai
+ Dettagli
+ Note
+ Eliminazione cartella \'%s\'
+ None
+ Label
+
+
+ Preferiti
+ Aggiungi preferiti
+ Aggiungi ai preferiti
+ Rimuovi dai preferiti
+
+
+ Cerca
+ Digita almeno 2 caratteri per iniziare la ricerca.
+
+
+ Filtro
+ Nessun elemento trovato.
+ Cambia filtro
+
+
+ L\'autorizzazione Archiviazione è necessaria
+ L\'autorizzazione Contatti è necessaria
+ L\'autorizzazione Fotocamera è necessaria
+ L\'autorizzazione Microfono è necessaria
+
+
+ Rinomina file
+ Rinomina cartella
+ Impossibile rinominare il file
+ Impossibile rinominare la cartella
+ Il nome della cartella non deve essere vuoto
+ Esiste già una cartella con questo nome
+ Non è possibile rinominare la cartella principale
+ Cartella rinominata correttamente
+ Rinominazione cartella
+ Il nome del file non deve essere vuoto
+ Il nome contiene caratteri non validi
+ Il nome del file \'%s\' contiene caratteri non validi
+ L\'estensione non può essere vuota
+ Il file di origine %s non esiste
+
+
+ Copia
+ Sposta
+ Copia / Sposta
+ Copia in
+ Sposta in
+ Origine
+ Destinazione
+ Seleziona destinazione
+ Clicca qui per selezionare la destinazione
+ Impossibile scrivere nella destinazione selezionata
+ Seleziona una destinazione
+ Origine e destinazione non possono essere uguali
+ Impossibile copiare i file
+ Copia in corso…
+ File copiati correttamente
+ Si è verificato un errore
+ Spostamento…
+ File spostati correttamente
+ Alcuni file non possono essere spostati
+ Alcuni file non possono essere copiati
+ Nessun file selezionato
+ Salvataggio…
+ Impossibile creare la cartella %s
+ Impossibile creare il file %s
+
+
+ Crea nuovo
+ Cartella
+ File
+ Crea nuova cartella
+ Esiste già un file o una cartella con quel nome
+ Il nome contiene caratteri non validi
+ Inserisci un nome
+ Riscontrato un errore sconosciuto
+
+
+ Il file \"%s\" esiste già
+ Il file \"%s\" esiste già. Sovrascrivi?
+ La cartella \"%s\" esiste già
+ Unisci
+ Sovrascrivi
+ Salta
+ Aggiungi \'_1\' alla fine
+ Applica a tutti i conflitti
+
+
+ Seleziona una cartella
+ Seleziona un file
+ Conferma l\'accesso all\'archiviazione esterna
+ Scegli la root della scheda SD nella prossima schermata, per consentire i permessi di scrittura
+ Se non hai una scheda SD, prova questo
+ Conferma selezione
+
+
+ - %d elemento
+ - %d elementi
+
+
+
+
+ - %d elemento
+ - %d elementi
+
+
+
+ - Eliminazione di %d elemento
+ - Eliminazione di %d elementi
+
+
+
+ Seleziona archiviazione
+ Interna
+ Scheda SD
+ Root
+ Selezionata una cartella errata, seleziona la cartella di root della tua scheda SD
+ I percorsi della scheda SD e del dispositivo OTG non possono essere uguali
+ Sembra che l\'app sia installata nella scheda SD, ciò impedisce la disponibilità dei widget dell\'app. Non li vedrai neppure nell\'elenco dei widget disponibili.
+ Se è una limitazione di sistema, quindi se vuoi usare i widget, devi spostare l\'app nella memoria interna.
+
+
+ Proprietà
+ Percorso
+ Elementi selezionati
+ File direttamente contenuti
+ Numero totale file
+ Risoluzione
+ Durata
+ Artista
+ Album
+ Lunghezza focale
+ Tempo di esposizione
+ Velocità ISO
+ Rapporto focale
+ Camera
+ EXIF
+ Titolo canzone
+
+
+ Colore di sfondo
+ Colore del testo
+ Colore primario
+ Colore in primo piano
+ Colore icona app
+ Ripristina predefiniti
+ Cambia colore
+ Tema
+ Modificando un colore, il tema diventerà personale
+ Salva
+ Scarta
+ Annulla modifiche
+ Sei sicuro di annullare le tue modifiche?
+ Hai modifiche non salvate. Salvare prima di uscire?
+ Applica i colori a tutte le app Simple
+ Colori aggiornati correttamente. È stato aggiunto un nuovo tema chiamato \'Condiviso\', usa quello per aggiornare tutti i colori dell\'app in futuro.
+
+ Simple Thank You per sbloccare questa funzione e supportare lo sviluppo. Grazie!
+ ]]>
+
+
+
+ Chiaro
+ Scuro
+ Solarizzato
+ Rosso scuro
+ Bianco e nero
+ Personale
+ Condiviso
+
+
+ Novità
+ * qui vengono elencate solo le modifiche maggiori, ci sono sempre anche altri piccoli miglioramenti
+
+
+ Elimina
+ Rimuovi
+ Rinomina
+ Condividi
+ Condividi via
+ Seleziona tutto
+ Nascondi
+ Non nascondere
+ Nascondi cartella
+ Non nascondere cartella
+ Mostra temporaneamente nascosti
+ Non mostrare media nascosti
+ Non puoi condividere così tanto in una volta sola
+ Svuota e disattiva il cestino
+ Annulla
+ Ripeti
+
+
+ Ordina per
+ Nome
+ Dimensione
+ Ultima modifica
+ Data acquisizione
+ Titolo
+ Nome file
+ Estensione
+ Ascendente
+ Discendente
+ Usa solo per questa cartella
+
+
+ Sei sicuro di procedere con l\'eliminazione?
+ Sei sicuro di volere eliminare %s?
+ Sei sicuro di volere spostare nel cestino %s?
+ Sei sicuro di volere eliminare questo elemento?
+ Sei sicuro di volere spostare questo elemento nel cestino?
+ Non chiedere nuovamente in questa sessione
+ Sì
+ No
+
+
+ - ATTENZIONE: stai per eliminare una cartella
+ - ATTENZIONE: stai per eliminare %d cartelle
+
+
+
+ PIN
+ Inserisci un PIN
+ Per favore inserisci un PIN
+ PIN errato
+ Ripeti il PIN
+ Sequenza
+ Inserisci una sequenza
+ Sequenza errata
+ Ripeti la sequenza
+ Impronta
+ Aggiungi un\'impronta
+ Appoggia il dito sul sensore di impronte digitali
+ Autenticazione fallita
+ Autenticazione bloccata, riprova fra qualche momento
+ Non hai impronte registrate, aggiungine alcune nelle impostazioni del tuo dispositivo
+ Vai alle impostazioni
+ Password impostata correttamente. Reinstalla l\'app nel caso la dimenticassi.
+ Protezione impostata correttamente. Reinstalla l\'app in caso di problemi nel ripristinarla.
+
+
+ Ieri
+ Oggi
+ Domani
+ secondi
+ minuti
+ ore
+ giorni
+
+
+ - %d secondo
+ - %d secondi
+
+
+ - %d minuto
+ - %d minuti
+
+
+ - %d ora
+ - %d ore
+
+
+ - %d giorno
+ - %d giorni
+
+
+ - %d settimana
+ - %d settimane
+
+
+ - %d mese
+ - %d mesi
+
+
+ - %d anno
+ - %d anni
+
+
+
+
+ - %d secondo
+ - %d secondi
+
+
+ - %d minuto
+ - %d minuti
+
+
+ - %d ora
+ - %d ore
+
+
+
+
+ - %d secondo prima
+ - %d secondi prima
+
+
+ - %d minuto prima
+ - %d minuti prima
+
+
+ - %d ora prima
+ - %d ore prima
+
+
+ - %d giorno prima
+ - %d giorni prima
+
+
+
+
+ - %d secondo
+ - %d secondi
+
+
+ - %d minuto
+ - %d minuti
+
+
+ - %d ora
+ - %d ore
+
+
+
+ Tempo rimanente prima che suoni la sveglia:\n%s
+ Tempo rimanente prima della notifica promemoria:\n%s
+ Assicurati che la sveglia funzioni correttamente prima di fare affidamento su di essa. Potrebbe comportarsi in modo anomalo a causa di restrizioni di sistema relative al risparmio della batteria.
+ Assicurati che i promemoria funzionino correttamente prima di fare affidamento su di essi. Potrebbero comportarsi in modo anomalo a causa di restrizioni di sistema relative al risparmio della batteria.
+
+
+ Sveglia
+ Posponi
+ Ignora
+ Nessun promemoria
+ All\'inizio
+ Suoni di sistema
+ I tuoi suoni
+ Aggiungi un nuovo suono
+ Nessun suono
+
+
+ Impostazioni
+ Acquista Simple Thank You
+ Personalizza i colori
+ Personalizza i colori del widget
+ Forza la lingua inglese
+ Non mostrare le novità all\'avvio
+ Mostra gli elementi nascosti
+ Dimensione carattere
+ Piccolo
+ Medio
+ Grande
+ Molto grande
+ Proteggi con password gli elementi nascosti
+ Proteggi con password tutta l\'applicazione
+ Mantieni data/ora ultima modifica in copia/sposta/rinomina
+ Mostra un fumetto informativo durante il trascinamento della barra di scorrimento
+ Impedisci allo schermo di spegnersi mentre l\'app è in primo piano
+ Non chiedere mai la conferma di eliminazione
+ Attiva tira-per-aggiornare dall\'alto
+ Usa il formato 24 ore
+ Inizio settimana di domenica
+ Widget
+ Usa sempre lo stesso tempo di ripetizione
+ Tempo di ripetizione
+ Vibra alla pressione dei tasti
+ Sposta gli elementi nel cestino invece di eliminarli
+ Intervallo svuotamento cestino
+ Svuota il cestino
+ Forza la modalità ritratto
+
+
+ Visibilità
+ Sicurezza
+ Scorrimento
+ Operazioni sui file
+ Cestino
+ Salvataggio
+ Avvio
+ Testo
+
+
+ Ripristina questo file
+ Ripristina file selezionati
+ Ripristina tutti i file
+ Il cestino è stato svuotato correttamente
+ I file sono stati ripristinati correttamente
+ Sei sicuro di volere svuotare il cestino? I file verranno persi definitivamente.
+ Il cestino è vuoto
+
+
+ Importazione…
+ Esportazione…
+ Importazione riuscita
+ Esportazione riuscita
+ Importazione fallita
+ Esportazione fallita
+ Importazione di alcuni elementi fallita
+ Esportazione di alcuni elementi fallita
+ Nessun elemento trovato da esportare
+
+
+ OTG
+ Scegli la cartella di root del dispositivo OTG nella prossima schermata, per consentire l\'accesso
+ Cartella selezionata errata, seleziona la cartella di root del dispositivo OTG
+
+
+ Gennaio
+ Febbraio
+ Marzo
+ Aprile
+ Maggio
+ Giugno
+ Luglio
+ Agosto
+ Settembre
+ Ottobre
+ Novembre
+ Dicembre
+
+
+ a gennaio
+ a febbraio
+ a marzo
+ ad aprile
+ a maggio
+ a giugno
+ a luglio
+ ad agosto
+ a settembre
+ ad ottobre
+ a novembre
+ a dicembre
+
+ Lunedì
+ Martedì
+ Mercoledì
+ Giovedì
+ Venerdì
+ Sabato
+ Domenica
+
+ L
+ M
+ M
+ G
+ V
+ S
+ D
+
+ Lun
+ Mar
+ Mer
+ Gio
+ Ven
+ Sab
+ Dom
+
+
+ Informazioni
+ Altre semplici app e codici sorgenti in
+ Invia la tua opinione o i tuoi suggerimenti a
+ Altre app
+ Licenze di terze parti
+ Invita un amico
+ Ciao, controlla %1$s su %2$s
+ Invita con
+ Dacci un voto sul Play Store
+ Dona
+ Dona
+ Seguici
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Informazioni aggiuntive
+ Versione app: %s
+ OS dispositivo: %s
+
+
+ spero che tu sia contento dell\'app. Non contiene pubblicità, per favore supportane lo sviluppo acquistando l\'app Simple Thank You . Inoltre questa finestra non verrà più mostrata.
+ Grazie!
+ ]]>
+
+ Acquista
+ Per favore aggiorna Simple Thank You all\'ultima versione
+ Prima di porre una domanda, leggi le domande frequenti,
+forse la soluzione è già lì.
+ Leggile
+
+
+
+
+ tanto per farti sapere che è uscita una nuova app di recente:
+ %2$s
+ Puoi scaricarla toccando il titolo.
+ Grazie
+ ]]>
+
+
+
+ Domande frequenti
+ Prima di porre una domanda, per favore leggi le
+ Come mai non vedo il widget di questa app nell\'elenco dei widget?
+ Probabilmente perchè hai spostato l\'app nella scheda SD. C\'è una limitazione di sistema di Android che nasconde i widget in questione
+ in quel caso. L\'unica soluzione è spostare l\'app nell\'archiviazione interna usando le impostazioni di sistema.
+ Vorrei darti supporto, ma non posso donare del denaro. Posso fare dell\'altro?
+ Sì, certo. Puoi spargere la voce riguardo le app o dare feedback e voti positivi. Puoi anche aiutare a tradurre le app in una nuova lingua, o aggiornare le traduzioni esistenti.
+ Puoi trovare la guida su https://github.com/SimpleMobileTools/General-Discussion , o scrivimi un messaggio a hello@simplemobiletools.com se ti serve aiuto.
+ Ho eliminato dei file per errore, come posso recuperarli?
+ Purtroppo non puoi. I file vengono eliminati subito dopo la richiesta di conferma, non c\'è nessun cestino disponibile.
+ Non mi piacciono i colori del widget, posso cambiarli?
+ Sì, quando trascini un widget nella tua schermata appare una finestra di configurazione. Vedrai dei quadrati colorati nell\'angolo in basso a sinistra, toccali per scegliere un colore. Puoi muovere l\'indicatore per regolare l\'alpha.
+ Posso recuperare in qualche modo i file eliminati?
+ Se sono stati veramente eliminati, non puoi. Tuttavia, puoi attivare nelle impostazioni l\'uso del cestino invece di eliminare. I file verranno solo spostati lì invece di essere eliminati.
+
+
+ Questa app usa le seguenti librerie di terze parti per semplificarmi la vita. Grazie.
+ Licenze di terze parti
+ Kotlin (linguaggio di programmazione)
+ PhotoView (immagini ingrandibili)
+ Glide (caricamento e caching immagini)
+ Picasso (caricamento e caching immagini)
+ Android Image Cropper (ritaglio e rotazione immagini)
+ RecyclerView MultiSelect (selezione elementi multipli da liste)
+ RtlViewPager (swipe da destra a sinistra)
+ Joda-Time (sostituto di \"Java date\")
+ Stetho (debug database)
+ Otto (bus eventi)
+ PhotoView (GIF zoomabili)
+ PatternLockView (sequenza di protezione)
+ Reprint (protezione impronta)
+ Gif Drawable (caricamento GIF)
+ AutoFitTextView (ridimensionamento testo)
+ Robolectric (ambiente di test)
+ Espresso (aiutante nei test)
+ Gson (parser JSON)
+ Leak Canary (rileva leak di memoria)
+ Number Picker (Selettore di numeri personalizzabile)
+ ExoPlayer (lettore video)
+ VR Panorama View (visualizzazione panorami)
+ Apache Sanselan (lettura metadati immagini)
+ Android Photo Filters (filtri immagine)
+
diff --git a/commons/src/main/res/values-iw/strings.xml b/commons/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000..b7db8b5
--- /dev/null
+++ b/commons/src/main/res/values-iw/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Cancel
+ Save as
+ File saved successfully
+ Invalid file format
+ Out of memory error
+ An error occurred: %s
+ Open with
+ No valid app found
+ Set as
+ Value copied to clipboard
+ Unknown
+ Always
+ Never
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Favorites
+ Add favorites
+ Add to favorites
+ Remove from favorites
+
+
+ Search
+ Type in at least 2 characters to start the search.
+
+
+ Filter
+ No items found.
+ Change filter
+
+
+ Storage permission is required
+ Contacts permission is required
+ Camera permission is required
+ Audio permission is required
+
+
+ Rename file
+ Rename folder
+ Could not rename the file
+ Could not rename the folder
+ Folder name must not be empty
+ A folder with that name already exists
+ Cannot rename the root folder of a storage
+ Folder renamed successfully
+ Renaming folder
+ Filename cannot be empty
+ Filename contains invalid characters
+ Filename \'%s\' contains invalid characters
+ Extension cannot be empty
+ Source file %s doesn\'t exist
+
+
+ Copy
+ Move
+ Copy / Move
+ Copy to
+ Move to
+ Source
+ Destination
+ Select destination
+ Click here to select destination
+ Could not write to the selected destination
+ Please select a destination
+ Source and destination cannot be the same
+ Could not copy the files
+ Copying…
+ Files copied successfully
+ An error occurred
+ Moving…
+ Files moved successfully
+ Some files could not be moved
+ Some files could not be copied
+ No files selected
+ Saving…
+ Could not create folder %s
+ Could not create file %s
+
+
+ Create new
+ Folder
+ File
+ Create new folder
+ A file or folder with that name already exists
+ The name contains invalid characters
+ Please enter a name
+ An unknown error occurred
+
+
+ File \"%s\" already exists
+ File \"%s\" already exists. Overwrite?
+ Folder \"%s\" already exists
+ Merge
+ Overwrite
+ Skip
+ Append with \'_1\'
+ Apply to all
+
+
+ Select a folder
+ Select a file
+ Confirm external storage access
+ Please choose the root folder of the SD card on the next screen, to grant write access
+ If you don\'t see the SD card, try this
+ Confirm selection
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Select storage
+ Internal
+ SD Card
+ Root
+ Wrong folder selected, please select the root folder of your SD card
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Properties
+ Path
+ Items selected
+ Direct children count
+ Total files count
+ Resolution
+ Duration
+ Artist
+ Album
+ Focal length
+ Exposure time
+ ISO speed
+ F-number
+ Camera
+ EXIF
+ Song title
+
+
+ Background color
+ Text color
+ Primary color
+ Foreground color
+ App icon color
+ Restore defaults
+ Change color
+ Theme
+ Changing a color will make it switch to Custom theme
+ Save
+ Discard
+ Undo changes
+ Are you sure you want to undo your changes?
+ You have unsaved changes. Save before exit?
+ Apply colors to all Simple Apps
+ Colors updated successfully. A new Theme called \'Shared\' has been added, please use that for updating all app colors in the future.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Light
+ Dark
+ Solarized
+ Dark red
+ Black & White
+ מותאם אישית
+ Shared
+
+
+ What\'s new
+ * only the bigger updates are listed here, there are always some smaller improvements too
+
+
+ Delete
+ Remove
+ Rename
+ Share
+ Share via
+ Select all
+ Hide
+ Unhide
+ Hide folder
+ Unhide folder
+ Temporarily show hidden
+ Stop showing hidden media
+ You cannot share this much content at once
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sort by
+ Name
+ Size
+ Last modified
+ Date taken
+ Title
+ Filename
+ Extension
+ Ascending
+ Descending
+ Use for this folder only
+
+
+ Are you sure you want to proceed with the deletion?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Do not ask again in this session
+ Yes
+ No
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Enter PIN
+ Please enter a PIN
+ Wrong PIN
+ Repeat PIN
+ Pattern
+ Insert pattern
+ Wrong pattern
+ Repeat pattern
+ Fingerprint
+ Add fingerprint
+ Please place your finger on the fingerprint sensor
+ Authentication failed
+ Authentication blocked, please try again in a moment
+ You have no fingerprints registered, please add some in the Settings of your device
+ Go to Settings
+ Password setup successfully. Please reinstall the app in case you forget it.
+ Protection setup successfully. Please reinstall the app in case of problems with reseting it.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minutes
+ hours
+ days
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+ - %d day
+ - %d days
+
+
+ - %d week
+ - %d weeks
+
+
+ - %d month
+ - %d months
+
+
+ - %d year
+ - %d years
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d minute before
+ - %d minutes before
+
+
+ - %d hour before
+ - %d hours before
+
+
+ - %d day before
+ - %d days before
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ No reminder
+ בהתחלה
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Settings
+ Purchase Simple Thank You
+ Customize colors
+ Customize widget colors
+ Use English language
+ Avoid showing What\'s New on startup
+ Show hidden items
+ Font size
+ Small
+ Medium
+ Large
+ Extra large
+ Password protect hidden item visibility
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ File operations
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importing…
+ Exporting…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ ינואר
+ פברואר
+ מרץ
+ אפריל
+ מאי
+ יוני
+ יולי
+ אוגוסט
+ ספטמבר
+ אוקטובר
+ נובמבר
+ דצמבר
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ Saturday
+ Sunday
+
+ ב\'
+ ג\'
+ ד\'
+ ה\'
+ ו\'
+ ש\'
+ א\'
+
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+
+
+ About
+ For the source codes visit
+ Send your feedback or suggestions to
+ More apps
+ Third party licences
+ Invite friends
+ Hey, come check out %1$s at %2$s
+ Invite via
+ Rate us
+ Donate
+ Donate
+ Follow us
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Additional info
+ App version: %s
+ Device OS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ This app uses the following third party libraries to make my life easier. Thank you.
+ Third party licences
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-ja/strings.xml b/commons/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000..c194e39
--- /dev/null
+++ b/commons/src/main/res/values-ja/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ キャンセル
+ 名前を付けて保存
+ ファイルを正常に保存しました
+ 使用できないファイル形式
+ メモリー不足エラー
+ エラーが発生しました: %s
+ 別のアプリで開く
+ 有効なアプリが見つかりません
+ 他で使う
+ 値をクリップボードにコピーしました
+ 不明
+ Always
+ Never
+ 詳細
+ メモ
+ フォルダ \'%s\' を削除しています
+ None
+ ラベル
+
+
+ お気に入り
+ お気に入りを追加
+ お気に入りに追加
+ お気に入りから削除
+
+
+ 検索
+ 検索を開始するには少なくとも2文字以上入力してください。
+
+
+ フィルタ
+ 何も見つかりませんでした。
+ フィルタを変更
+
+
+ ストレージへのアクセス許可が必要です
+ 連絡先へのアクセス許可が必要です
+ カメラへのアクセス許可が必要です
+ マイクへのアクセス許可が必要です
+
+
+ ファイルの名前を変更
+ フォルダの名前を変更
+ ファイルの名前を変更できませんでした
+ フォルダの名前を変更できませんでした
+ フォルダ名は空にできません
+ その名前のフォルダは既に存在します
+ ストレージのルートフォルダの名前を変更できません
+ フォルダの名前を正常に変更しました
+ フォルダの名前を変更中
+ ファイル名は空にできません
+ ファイル名に無効な文字が含まれています
+ ファイル名 \'%s\' に無効な文字が含まれています
+ 拡張子は省略できません
+ ソースファイル %s が存在しません
+
+
+ コピー
+ 移動
+ コピー / 移動
+ コピー
+ 移動
+ 元
+ 先
+ 宛先を選択
+ クリックして宛先を選択
+ 選択した宛先に書き込みできませんでした
+ 宛先を選択してください
+ 元と先を同じにすることはできません
+ ファイルをコピーできませんでした
+ コピー中…
+ ファイルを正常にコピーしました
+ エラーが発生しました
+ 移動中…
+ ファイルを正常に移動しました
+ 一部のファイルが移動できませんでした
+ 一部のファイルがコピーできませんでした
+ ファイルが選択されていません
+ 保存中…
+ フォルダ %s を作成できません
+ ファイル %s を作成できません
+
+
+ 新規作成
+ フォルダ
+ ファイル
+ フォルダ新規作成
+ 同名のファイルまたはフォルダが既に存在しています
+ 名前に無効な文字が含まれています
+ 名前を入力してください
+ 不明なエラーが発生しました
+
+
+ ファイル \"%s\" は既に存在しています
+ ファイル \"%s\" は既に存在しています。上書きしますか?
+ フォルダ \"%s\" は既に存在しています
+ 統合
+ 上書き
+ スキップ
+ \'_1\' に追記する
+ すべてに適用
+
+
+ フォルダを選択
+ ファイルを選択
+ 外部ストレージのアクセスを確認してください
+ 次の画面で SDカードのルートフォルダを選択して、書き込みアクセス許可を付与してください
+ SDカードの内容が表示されない場合は、こちらをお試しください
+ Confirm selection
+
+
+ - %d アイテム
+ - %d アイテム
+
+
+
+
+ - %d アイテム
+ - %d アイテム
+
+
+
+ - %d アイテムを削除しています
+ - %d アイテムを削除しています
+
+
+
+ ストレージを選択
+ 内部
+ SDカード
+ ルート
+ フォルダの選択が正しくありません。SDカードを選択してください
+ SDカードとOTGデバイスのパスを同じにすることはできません
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ プロパティ
+ パス
+ アイテム選択
+ 直接の子の数
+ 合計ファイル数
+ 解像度
+ 期間
+ アーティスト
+ アルバム
+ 焦点距離
+ 露出時間
+ ISO感度
+ 絞り値
+ カメラ名
+ EXIF
+ 曲名
+
+
+ 背景色
+ 文字色
+ メインカラー
+ 前景色
+ App icon color
+ デフォルトに戻す
+ Change color
+ テーマ
+ 色を変更すると、テーマが「カスタム」に切り替わります
+ 保存
+ 破棄
+ 変更を元に戻す
+ 変更を元に戻してもよろしいですか?
+ 保存していない変更があります。保存しますか?
+ Apply colors to all Simple Apps
+ Colors updated successfully. A new Theme called \'Shared\' has been added, please use that for updating all app colors in the future.
+
+ Simple Thank You アプリを購入してください。
+ ]]>
+
+
+
+ ライト
+ ダーク
+ Solarized
+ Dark red
+ モノクロ
+ カスタム
+ Shared
+
+
+ 更新内容
+ * ここでは主な変更点を挙げていますが、その他の細かい改善も行われています
+
+
+ 削除
+ Remove
+ 名前の変更
+ 共有
+ 共有
+ すべて選択
+ 非表示
+ 再表示
+ フォルダを非表示
+ フォルダを再表示
+ 非表示の項目を一時的に表示
+ 非表示項目を表示しない
+ 一度に複数のコンテンツを共有することはできません
+ ごみ箱を空にして無効にする
+ Undo
+ Redo
+
+
+ 並べ替え
+ 名前
+ サイズ
+ 最終更新日時
+ 撮影日時
+ タイトル
+ ファイル名
+ 拡張子
+ 昇順
+ 降順
+ このフォルダのみに適用
+
+
+ 削除を続行してもよろしいですか?
+ %s を削除してもよろしいですか?
+ %s をゴミ箱に移動してもよろしいですか?
+ このアイテムを削除してもよろしいですか?
+ 個のアイテムをゴミ箱に移動してもよろしいですか?
+ このセッションでは再度たずねない
+ はい
+ いいえ
+
+
+ - 警告: フォルダを削除しようとしています
+ - 警告: %d 個のフォルダを削除しようとしています
+
+
+
+ PIN
+ PINを入力
+ PINを入力してください
+ PINに誤りがあります
+ PINを再入力
+ パターン
+ パターンを入力
+ パターンに誤りがあります
+ パターンを再入力
+ 指紋
+ 指紋を追加
+ 指紋センサーに指を置いてください
+ Authentication failed
+ Authentication blocked, please try again in a moment
+ You have no fingerprints registered, please add some in the Settings of your device
+ 設定に移動
+ パスワードの設定が完了しました。パスワードを紛失した場合は、アプリを再インストールしてください
+ Protection setup successfully. Please reinstall the app in case of problems with reseting it.
+
+
+ 昨日
+ 今日
+ 明日
+ 秒
+ 分
+ 時間
+ 日
+
+
+ - %d秒
+ - %d秒
+
+
+ - %d分
+ - %d分
+
+
+ - %d時間
+ - %d時間
+
+
+ - %d日
+ - %d日
+
+
+ - %d週
+ - %d週
+
+
+ - %dヶ月
+ - %dヶ月
+
+
+ - %d年
+ - %d年
+
+
+
+
+ - %d秒
+ - %d秒
+
+
+ - %d分
+ - %d分
+
+
+ - %d時間
+ - %d時間
+
+
+
+
+ - %d秒前
+ - %d秒前
+
+
+ - %d分前
+ - %d分前
+
+
+ - %d時間前
+ - %d時間前
+
+
+ - %d日前
+ - %d日前
+
+
+
+
+ - %d秒
+ - %d秒
+
+
+ - %d分
+ - %d分
+
+
+ - %d時
+ - %d時
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ No reminder
+ 開始時
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ 設定
+ Purchase Simple Thank You
+ 表示色のカスタマイズ
+ ウィジェットの色をカスタマイズ
+ 英語で表示する
+ 起動時に更新履歴を表示しない
+ Show hidden items
+ メモのフォントサイズ
+ 小
+ 通常
+ 大
+ 特大
+ 非表示にした項目をパスワードで保護する
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ 24時間表示を使用
+ Start week on Sunday
+ ウィジェット
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ 削除の代わりにアイテムをごみ箱に移動する
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ ファイル操作
+ ごみ箱
+ 保存中
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importing…
+ Exporting…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ 1月
+ 2月
+ 3月
+ 4月
+ 5月
+ 6月
+ 7月
+ 8月
+ 9月
+ 10月
+ 11月
+ 12月
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ 月曜日
+ 火曜日
+ 水曜日
+ 木曜日
+ 金曜日
+ 土曜日
+ 日曜日
+
+ 月
+ 火
+ 水
+ 木
+ 金
+ 土
+ 日
+
+ 月
+ 火
+ 水
+ 木
+ 金
+ 土
+ 日
+
+
+ このアプリについて
+ ソースコードはこちら
+ ご意見やご提案をお送りください
+ 他のアプリ
+ サードパーティー ライセンス
+ 友達を招待
+ %2$s で %1$s を確認してください
+ 招待
+ Playストアで評価してください
+ 寄付
+ 寄付
+ フォローしてください
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ 追加情報
+ アプリバージョン: %s
+ デバイスOS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ \"Simple Thank You\" アプリを最新版に更新してください
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ このアプリは、私の暮らしにゆとりを持たせるために、次のサードパーティのライブラリーを使用しています。ありがとうございます。
+ サードパーティー ライセンス
+ Kotlin (プログラミング言語)
+ Subsampling Scale Image View (ズーム可能画像ビュー)
+ Glide (画像ローディングとキャッシング)
+ Picasso (画像ローディングとキャッシング)
+ Android Image Cropper (画像のトリミングと回転)
+ RecyclerView MultiSelect (複数選択リストアイテム)
+ RtlViewPager (右から左へのスワイプ)
+ Joda-Time (Java date代替)
+ Stetho (データベースのデバッグ)
+ Otto (イベントバス)
+ PhotoView (GIF画像のズーム表示)
+ PatternLockView (パターン認証)
+ Reprint (fingerprint protection)
+ Gif Drawable (GIF画像の読み込み)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (メモリリークの検出)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-ko-rKR/strings.xml b/commons/src/main/res/values-ko-rKR/strings.xml
new file mode 100644
index 0000000..4ed9e85
--- /dev/null
+++ b/commons/src/main/res/values-ko-rKR/strings.xml
@@ -0,0 +1,564 @@
+
+ 확인
+ 취소
+ 다른이름으로 저장
+ 파일저장 성공
+ 잘못된 파일 형식
+ 메모리 부족 오류
+ 에러발생: %s
+ 연결 애플리케이션
+ 연결 가능한 애플리케이션 없음
+ 애플리케이션에 설정
+ 클립보드에 복사됨
+ 알 수 없음
+ Always
+ 사용 안함
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ 즐겨찾기
+ Add favorites
+ 즐겨찾기에 추가
+ 즐겨찾기에서 제거
+
+
+ 검색
+ Type in at least 2 characters to start the search.
+
+
+ 필터
+ 항목을 찾지 못함
+ 필터 변경
+
+
+ 저장 권한이 필요함
+ 주소록 사용 권한이 필요함
+ 카메라 사용 권한이 필요함
+ 오디오 사용 권한이 필요함
+
+
+ 파일명 변경
+ 폴더명 변경
+ 파일명을 변경 할 수 없음
+ 폴더명을 변경 할 수 없음
+ 폴더명은 반드시 입력되어야 함
+ 폴더명이 이미 존재함
+ 저장소의 루트폴더 이름은 변경 할 수 없음
+ 폴더명 변경 성공
+ 폴더명 변경
+ 파일명이 비어있으면 안됨
+ 파일명에 사용할 수 없는 문자가 포함됨
+ Filename \'%s\' contains invalid characters
+ 확장자가 비어있으면 안됨
+ Source file %s doesn\'t exist
+
+
+ 복사
+ 이동
+ 복사 / 이동
+ 복사하기
+ 이동하기
+ 원본
+ 대상
+ 대상 선택
+ 대상을 선택하려면 여기를 클릭하세요.
+ 대상경로에 생성 할 수 없음
+ 대상 경로를 선택하세요.
+ 원본파일과 대상파일은 동일할 수 없음
+ 파일을 복사 할 수 없음
+ 복사중…
+ 파일복사 성공
+ 에러발생
+ 이동중…
+ 파일이동 성공
+ 일부 파일을 이동 할 수 없음
+ 일부 파일을 복사 할 수 없음
+ 파일이 선택되지 않음
+ 저장중…
+ %s 폴더 생성에 실패함
+ %s 파일 생성에 실패함
+
+
+ 새로 만들기
+ 폴더
+ 파일
+ 새로운 폴더 만들기
+ 동일한 이름의 파일 또는 폴더가 이미 존재함
+ 이름에 사용 할 수 없는 문자가 포함됨
+ 이름을 입력 하세요.
+ 알 수 없는 에러가 발생함
+
+
+ \"%s\" 파일이 이미 존재합니다.
+ \"%s\" 파일이 이미 존재합니다. 덮어쓰시겠습니까?
+ Folder \"%s\" already exists
+ Merge
+ 덮어쓰기
+ 건너뛰기
+ \'_1\' 문자열을 추가함
+ 모든 출돌에 적용
+
+
+ 폴더 선택
+ 파일 선택
+ 외장 메모리 접근허용
+ 쓰기 권한을 부여하려면 다음 화면에서 SD 카드의 루트 폴더를 선택하세요.
+ SD 카드가 보이지 않으면 시도해보세요.
+ Confirm selection
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ 저장공간 선택
+ 내장 메모리
+ 외장 메모리
+ 루트
+ 잘못된 폴더가 선택되었습니다. SD 카드의 루트 폴더를 선택하세요.
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ 속성
+ 경로
+ 선택된 항목 수
+ Direct children count
+ 전체 파일 수
+ 해상도
+ Duration
+ Artist
+ Album
+ Focal length
+ Exposure time
+ ISO speed
+ F-number
+ Camera
+ EXIF
+ Song title
+
+
+ 배경 색상
+ 텍스트 색상
+ 주요 색상(테마)
+ Foreground color
+ App icon color
+ 기본설정으로 변경
+ Change color
+ 테마
+ 주요 색상(테마)을 변경하면 사용자정의 테마로 전환됩니다.
+ 저장
+ 취소
+ 변경사항 되돌리기
+ 변경된 사항을 되돌리겠습니까?
+ 변경사항을 저장하지 않았습니다. 종료전에 저장 하시겠습니까?
+ 심플 시리즈 애플리케이션에 일괄적용
+ 색상이 성공적으로 변경되었습니다. \'공유\'라는 새로운 테마가 추가되었습니다. 앞으로 모든 앱 색상을 변경할때 사용하세요.
+
+ Simple Thank You 앱을 구매해주세요. 감사합니다!
+ ]]>
+
+
+
+ Light
+ Dark
+ Solarized
+ Dark red
+ Black & White
+ 사용자 정의
+ Shared
+
+
+ 새로운 기능
+ * only the bigger updates are listed here, there are always some smaller improvements too
+
+
+ 삭제
+ Remove
+ 이름변경
+ 공유
+ 공유방법
+ 전체선택
+ 숨김
+ Unhide
+ 폴더 숨김
+ Unhide folder
+ 숨김파일 임시보기
+ 숨김파일 임시보기 해제
+ 한 번에 많은 콘텐츠를 공유 할 수 없습니다.
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ 정렬 방식
+ 이름
+ 크기
+ 최종 수정시간
+ 촬영 날짜
+ 제목
+ 파일명
+ 파일종류
+ 오름차 순
+ 내림차 순
+ 현재 폴더만 적용
+
+
+ 정말로 삭제 하시겠습니까?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ 다시 물어보지 않음
+ 네
+ 아니요
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ PIN 번호 입력
+ PIN 번호를 입력하세요.
+ PIN 번호가 일치하지 않습니다.
+ PIN 번호 확인
+ 패턴
+ 패턴 입력
+ 패턴이 잘못 입력되었습니다.
+ 패턴 확인
+ 지문
+ 지문 추가
+ 지문 센서에 손가락을 올려주세요.
+ 인증 실패
+ 인증이 차단 되었으니, 잠시 후 다시 시도하십시오.
+ 등록 된 지문이 없습니다. 기기의 설정에서 지문을 추가하십시오.
+ 설정으로 이동
+ 암호 설정이 완료되었습니다. 암호를 기억하지 못하는경우 앱을 다시 설치하시기 바랍니다.
+ 보안 설정이 성공적으로 완료되었습니다. 재설정 시 문제가 발생하면 앱을 다시 설치하시기 바랍니다.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ 분
+ 시간
+ 일
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+ - %d day
+ - %d days
+
+
+ - %d week
+ - %d weeks
+
+
+ - %d month
+ - %d months
+
+
+ - %d year
+ - %d years
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d 분 전
+ - %d 분 전
+
+
+ - %d 시간 전
+ - %d 시간 전
+
+
+ - %d 일 전
+ - %d 일 전
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d 분
+ - %d 분
+
+
+ - %d 시
+ - %d 시
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ 스누즈
+ Dismiss
+ 알림 없음
+ 시작할 때
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ 설정
+ Purchase Simple Thank You
+ 앱 테마 및 색상 설정
+ Customize widget colors
+ 영어 사용
+ 시작시 새로운 기능 표시 안 함
+ 숨김항목 활성화
+ 폰트 크기
+ 작게
+ 중간
+ 크게
+ 아주 크게
+ 숨김파일 보기 암호설정
+ 애플리케이션 전체에서 암호를 이용한 보호기능 사용
+ 파일 복사/이동/변경 시 최종 수정시간 정보를 유지
+ 스크롤바 드래그 시 항목별 툴팁 활성화
+ Prevent phone from sleeping while the app is in foreground
+ 삭제 확인 다이얼로그 비활성화
+ 상하단 스와이프 동작으로 새로고침
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ 가시성
+ 보안
+ 스크롤링
+ 파일 작업
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ 가져오는 중…
+ 내보내는 중…
+ 가져오기 성공
+ 내보내기 성공
+ 가져오기 실패
+ 내보내기 실패
+ 일부항목 가져오기에 실패함
+ 일부항목 내보내기에 실패함
+ 내보낼 수 있는 항목을 찾을 수 없음
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ 1월
+ 2월
+ 3월
+ 4월
+ 5월
+ 6월
+ 7월
+ 8월
+ 9월
+ 10월
+ 11월
+ 12월
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ 월요일
+ 화요일
+ 수요일
+ 목요일
+ 금요일
+ 토요일
+ 일요일
+
+ 월
+ 화
+ 수
+ 목
+ 금
+ 토
+ 일
+
+ 월요일
+ 화요일
+ 수요일
+ 목요일
+ 금요일
+ 토요일
+ 일요일
+
+
+ 앱정보
+ 소스코드 공개 링크
+ 피드백 또는 제안요청
+ 앱 더보기
+ 서드파티 라이선스
+ 친구에게 공유
+ %1$s 애플리케이션을 다운받으세요. %2$s
+ 애플리케이션 선택
+ 앱 평가하기
+ 기부
+ 기부하기
+ Follow us
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Additional info
+ App version: %s
+ Device OS: %s
+
+
+ 우리의 애플리케이션이 도움이 되길 바랍니다.
+ 사용하고 계신 애플리케이션은 광고 및 인앱 결제가 없는 무료 애플리케이션이므로 별도의 수익이 발생하지 않습니다.
+ 당신이 원하신다면 개발지원을 위해 Simple Thank You 앱을 다운받아 지원 하실 수 있습니다.
+ \'Simple Thank You\' 앱이 설치되면 이 다이얼로그는 비 활성화 됩니다.
+ 감사합니다.
+ ]]>
+
+ 구매
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ 자주 묻는 질문
+ Before you ask a question, please first read the
+ 위젯 목록에 이앱이 표시되지 않으면 어떻게 해야 하나요?
+ SD 카드에서 앱을 이동했기 때문에 그럴 가능성이 큽니다. 그런경우 안드로이드 시스템 정책에 따라 위젯이 보이지 않게됩니다.
+ 유일한 해결책은 기기 설정을 통해 앱을 내부 저장소로 다시 이동하는 것입니다.
+ 기부가 아닌 다른 방법으로 당신을 지원하고 싶습니다. 다른 방법이 있나요?
+ 예, 물론입니다. 앱에 대한 의견을 전하거나 좋은 피드백과 평가를 제공 할 수 있습니다. 또한 새로운 언어로 앱을 번역하거나 기존 번역을 업데이트하면 도움이됩니다.
+ 가이드는 https://github.com/SimpleMobileTools/General-Discussion에서 찾을 수 있습니다. 도움이 필요하면 hello@simplemobiletools.com으로 메시지를 보내시기 바랍니다.
+ 실수로 일부 파일을 삭제했는데 어떻게 복구 할 수 있습니까?
+ 불행하게도 불가능합니다. 파일은 바로 삭제되며 사용 가능한 휴지통이 없습니다.
+ 위젯 색상이 마음에 들지 않을 경우 변경할 수 있나요?
+ 예, 홈 화면에서 위젯을 드래그하면 위젯 구성 화면이 나타납니다. 왼쪽 하단 모서리에 색깔이있는 사각형이 보일 것입니다. 그것을 눌러 새로운 색을 선택하십시오. 슬라이더를 사용하여 알파를 조정할 수도 있습니다.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ This app uses the following third party libraries to make my life easier. Thank you.
+ 서드파티 라이선스
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-lt/strings.xml b/commons/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..b7e26b6
--- /dev/null
+++ b/commons/src/main/res/values-lt/strings.xml
@@ -0,0 +1,575 @@
+
+ Gerai
+ Atšaukti
+ Išsaugoti kaip
+ Byla išsaugota sėkmingai
+ Neteisingas bylos formatas
+ Klaida: trūksta atminties
+ Klaida: %s
+ Atidaryti su
+ Nerasta tinkamos programėlės
+ Nustatyti kaip
+ Reikšmė nukopijuota į iškarpinė
+ Nežinoma
+ Visada
+ Niekada
+ Detalės
+ Užrašai
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Mėgiamiausieji
+ Pridėti mėgiamiausiuosius
+ pridėti į mėgiamiausiuosius
+ Pašalinti iš mėgiamiausiųjų
+
+
+ Ieškoti
+ Įvesti mažiausiai 2 simbolius, kad pradėti paiešką.
+
+
+ Filtras
+ Nerasta elementų.
+ Pakeisti filtrą
+
+
+ Reikalingas saugyklos leidimas
+ Reikalingas kontaktų leidimas
+ Reikalingas fotoaparato leidimas
+ Reikalingas garso leidimas
+
+
+ Pervadinti bylą
+ Pervadinti aplanką
+ Byla negali būti pervadnta
+ Aplankas negali būti pervadintas
+ Aplanko pavadinimas negali būti paliktas tuščias
+ Aplankas šiuo vardu jau yra
+ Negalima pervadinti pagrindinio talpyklos aplanko
+ Aplankas pervadintas sėkmingai
+ Aplankas pervadinamas
+ Bylos pavadinimas negali būti paliktas tusčias
+ Bylos pavadinime yra negalimų simbolių
+ Bylos pavadinimas \'%s\' susideda iš negalimų simbolių
+ Papildinys negali būti paliktas tuščias
+ Šaltinio byla %s neegzistuoja
+
+
+ Kopijuoti
+ Perkelti
+ Kopijuoti / Perkelti
+ Kopijuoti į
+ Perkelti į
+ Šaltinis
+ Vieta
+ Pasirinkite vietą
+ Pasirinkite nustatyti vietą
+ Pasirinktoje vietoje negalima rašyti
+ Pasirinkite vietą
+ Šaltinis ir vieta negali sutapti
+ Bylos negali būti kopijuojamos
+ Kopijuojama…
+ Sėkmingai padarytos bylų kopijos
+ Klaida
+ Perkeliama…
+ Bylos perkeltos sėkmingai
+ Kai kurios bylos negali būti perkeltos
+ Kai kurios bylos negali būti kopijuojamos
+ Nepasirinktos bylos
+ Išsaugoma…
+ Negalima sukurti aplanko %ų
+ Negalima sukurti bylos %ų
+
+
+ Kurti naują
+ Aplanką
+ Bylą
+ Kurti naują aplanką
+ Byla ar aplankas tokiu pavadinimu jau yra
+ Pavadinimas turi negalimų simbolių
+ Įveskite pavadinimą
+ Nežinoma klaida
+
+
+ Byla \"%s\" jau yra
+ Byla \"%s\" jau yra. Pervadinti?
+ Aplankalas \"%ai\" jau yra
+ Sulieti
+ Užrašyti ant viršaus
+ Praleisti
+ Papildyti su \'_1\'
+ Taikyti visiems neatitikimams
+
+
+ Pasirinkti aplanką
+ Pasirinkti byla
+ Pasirinkti išorinės talpyklos prieigą
+ Norint suteikti prieigą, pasirinkite pagrindinį SD kortelės aplanką kitame lange
+ Jei nematote SD kortelės, pabandykite tai
+ Patvirtinti pasirinkimą
+
+
+ - %d elementas
+ - %d elementai
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Pasirinkti talpyklą
+ Vidinė
+ SD kortelė
+ Pagrindinė
+ Pasirinktas neteisingas aplankas, pasirinkite pagrindinį SD kortelės aplanką
+ SD kortelė ir OTG įrenginio keliai negali būti tokie patys
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Savybės
+ Kelias
+ Pasirinkti elementai
+ Tiesioginis skaičius
+ Visas bylų skaičius
+ Raiška
+ Trukmė
+ Atlikėjas
+ Albumas
+ Židinio nuotolis
+ Atlikimo laikas
+ ISO greitis
+ F-numeris
+ Kamera
+ EXIF
+ Dainos pavadinimas
+
+
+ Fono spalva
+ Teksto spalva
+ Pirminė spalva
+ Priekinio plano spalva
+ Programėlės ikonėlės spalva
+ Atstatyti
+ Change color
+ Tema
+ Keičiant temą bus perjungta į pasirinktiną temą
+ Išsaugoti
+ Atmesti
+ Panaikinti pakeitimus
+ Ar esate tikri, jog norite panaikinti pakeitimus?
+ Turite neišsaugotų pakeitimų. Išsaugoti prieš išeinant?
+ Pritaikyti spalvas visoms paprastoms programėlėms
+ Spalvos atnaujintos sėkmingai. Nauja tema pavdinimu \'Bendra\' buvo įtraukta, prašome naudoti tai ateityje visų programėlių spalvų atnaujinimui.
+
+ Paprastas ačiūatrakinti šią funkciją ir palaikyti vystymą. Ačiū!
+ ]]>
+
+
+
+ Šviesi
+ Tamsi
+ Soliarizuota
+ Tamsiai raudona
+ Juoda ir balta
+ Pasirinktinė
+ Bendra
+
+
+ Kas naujo
+ * Čia paminėti tik svarbesni atnaujinimai, bet visada yra smulkesnių
+
+
+ Ištrinti
+ Pašalinti
+ Pervadinti
+ Dalintis
+ Dalintis per
+ Pasirinkti viską
+ Slėpti
+ Neslėpti
+ Slėpti aplanką
+ Neslėpti aplanko
+ Laikinai rodyti paslėptus
+ Neberodyti paslėptų bylų
+ Negalite bendrinti tiek daug turinio vienu metu
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Rūšiuoti pagal
+ Vardą
+ Dydį
+ Paskutinį redagavimą
+ Sukūrimo datą
+ Pavadinimą
+ Bylos pavadinimą
+ Papildinį
+ Didėjančiai
+ Mažėjančiai
+ Taikyti tik šiam aplankui
+
+
+ Ar tikrai norite ištrinti?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Do not ask again in this session
+ Taip
+ Ne
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Įvesti PIN
+ Prašome įvesti PIN
+ Klaidingas PIN
+ Pakartoti PIN
+ Modelis
+ Įtraukti modelį
+ Klaidingas modelis
+ Pakartoti modelį
+ Piršto atspaudas
+ Pridėti piršto atspaudą
+ Prašome uždėti savo pirštą ant pirštų atspaudų jutiklio
+ Autentifikacija nepavyko
+ Autentifikacija blokuojama, prašome pabandyti iš naujo vėliau
+ Neturite registruotų pirštų atspaudų, prašome įtraukti Jūsų įrenginio nustatymuose
+ Eiti į nustatymus
+ Slaptažodis nustatytas sėkmingai. Prašome įdiegti programėlę iš naujo jei pamiršite slaptažodį.
+ Apsauga nustatyta sėkmingai. Prašome įdiegti programėlę iš naujo jei bus problemų su apsaugos nustatymu iš naujo.
+
+
+ Yesterday
+ Today
+ Rytoj
+ sekundės
+ minutės
+ valandos
+ dienos
+
+
+ - %d sekundė
+ - %d sekundės
+ - %d sekundžių
+
+
+ - %d minutė
+ - %d minutės
+ - %d minučių
+
+
+ - %d valanda
+ - %d valandos
+ - %d valandų
+
+
+ - %d diena
+ - %d dienos
+ - %d dienų
+
+
+ - %d savaitė
+ - %d savaitės
+ - %d savaičių
+
+
+ - %d mėnuo
+ - %d mėnesiai
+ - %d mėnesių
+
+
+ - %d metai
+ - %d metai
+ - %d metų
+
+
+
+
+ - per %d sekundę
+ - per %d sekundes
+ - per %d sekundžių
+
+
+ - per %d minutę
+ - per %d minutes
+ - per %d minučių
+
+
+ - per %d valandą
+ - per %d valandas
+ - per %d valandų
+
+
+
+
+ - Prieš %d sekundę
+ - Prieš %d sekundes
+ - Prieš %d sekundžių
+
+
+ - Prieš %d minutę
+ - Prieš %d minutes
+ - Prieš %d minučių
+
+
+ - Prieš %d valandą
+ - Prieš %d valandas
+ - Prieš %d valandų
+
+
+ - Prieš %d dieną
+ - Prieš %d dienas
+ - Prieš %d dienų
+
+
+
+
+ - Per %d sekundę
+ - Per %d sekundes
+ - Per %d sekundžių
+
+
+ - Per %d minutę
+ - Per %d minutes
+ - Per %d minučių
+
+
+ - Per %d valandą
+ - Per %d valandas
+ - Per %d valandų
+
+
+
+ Likęs laikas iki žadintuvo suveikimo:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Prieš naudodamiesi, įsitikinkite, kad signalas veikia tinkamai. Gali nesuveikti dėl sistemos apribojimų, susijusių su baterijos taupymu.
+ Prieš naudodamiesi, įsitikinkite, kad priminimai tinkamai veikia. Jie gali netinkamai veikti dėl sistemos apribojimų, susijusių su baterijos taupymu.
+
+
+ Žadintuvas
+ Snausti
+ Nutraukti
+ Nėra priminimo
+ Paleidžiant
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Nustatymai
+ Purchase Simple Thank You
+ Spalvų redagavimas
+ Customize widget colors
+ Naudoti anglų kalbą
+ Nerodyti "Kas nauja" paleidus programėlę
+ Rodyti paslėptus elementus
+ Teksto dydis
+ Smulkus
+ Vidutinis
+ Didelis
+ Ypač didelis
+ Apsaugoti slaptažodžiu paslėptų elementų matomumą
+ Apsaugoti slaptažodžiu visą programėlę
+ Saugiti seną paskiausiai pakeista reikšmę kopijuojant/perkeliant/pervardinant
+ Rodyti informacijos burbuliuką esant slinkties elementams velkant slinkties juostą
+ Neleisti telefonui užmigti, kai programėlė veikia fone
+ Visada praleisti ištrynimo patvirtinimo dialogą
+ Įgalinti patraukimą nuo viršaus, kad
+ Naudoti 24 valandų formatą
+ Pradėti savaitę nuo sekmadienio
+ Valdikliai
+ Visada naudoti tą patį snaudimo laiką
+ Snaudimo laikas
+ Vibruoti kai spaudžiami mygtukai
+ Perkelti elementus į šiukšlinę vietoj ištrynimo
+ Šiukšlinės išvalymo intervalas
+ Ištuštinti šiukšlinę
+ Priversti rodyti portreto rėžimu
+
+
+ Matomumas
+ Sauga
+ Slinkimas
+ Bylų operacijos
+ Šiukšlinė
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importuojama…
+ Eksportuojama…
+ Importuota sėkmingai
+ Eksportuota sėkmingai
+ Importavimas nepavyko
+ Eksportavimas nepavyko
+ Kai kurių įrašų importuoti nepavyko
+ Kai kurių įrašų eksportuoti nepavyko
+ Nerasta įrašų eksportavimui
+
+
+ OTG
+ Prašome pasirinkti šakninį OTG įrenginio aplanką kitame lange, kad patvirtinti prieigą
+ Pasirinktas netinkamas aplankas, pasirinkite OTG įrenginio šakninį aplanką
+
+
+ Sausis
+ Vasaris
+ Kovas
+ Balandis
+ Gegužė
+ Birželis
+ Liepa
+ Rugpjūtis
+ Rugsėjis
+ Spalis
+ Lapkritis
+ Gruodis
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Pirmadienis
+ Antradienis
+ Trečiadienis
+ Ketvirtadienis
+ Penktadienis
+ Šeštadienis
+ Sekmadienis
+
+ P
+ A
+ T
+ K
+ P
+ Š
+ S
+
+ Pir
+ Ant
+ Tre
+ Ket
+ Pen
+ Šeš
+ Sek
+
+
+ Apie
+ Pirminį kodą rasite
+ Siųskite atsiliepimus ar pasiūlymus adresu
+ Daugiau programėlių
+ Trečiųjų šalių licensijos
+ Pakviesti draugus
+ Sveikas, pažiūrėk %1$s %2$s
+ Pakviesti per
+ Įvertinti mus
+ Paaukoti
+ Sekti mus
+ v %1$s\nVisos teisės saugomos© Paprasti mobilūs įrankiai %2$d
+ Papildoma informacija
+ Programėlės versija: %s
+ Įrenginio OS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Dažniausiai užduodami klausimai
+ Before you ask a question, please first read the
+ Kodėl aš negaliu pamatyti šios programėlės valdiklių sąraše?
+ Labiausiai tikėtina, kad perkėlėte programėlę į SD kortelę. Yra "Android" sistemos apribojimas, kuris slepia nurodytos programėlės valdiklį tuo atveju. Vienintelis sprendimas yra perkelti programėlę į vidinę atmintinę per įrenginio nustatymus.
+ Aš noriu jus paremti, bet aš negaliu paaukoti pinigų. Ar yra kažkas, ką galiu padaryti?
+ Taip, žinoma. Galite skleisti žodį apie programėles arba pateikti gerus atsiliepimus ir įvertinimus. Taip pat galite padėti verčiant programėles nauja kalba arba tiesiog atnaujinkite keletą esamų vertimų.Vadovą galite rasti adresu https://github.com/SimpleMobileTools/General-Discussion, arba tiesiog rašykite man žinutę hello@simplemobiletools.com, jei jums reikia pagalbos.
+ Aš per klaidą ištryniau keletą bylų, kaip jas atkurti?
+ Deja, jūs negalite. Bylos iš karto ištrinamos po patvirtinimo dialogo, nėra šiukšliadėžės.
+ Aš nemėgstu valdiklio spalvų, galiu jas pakeisti?
+ Taip, kai vilksite valdiklį savo pagrindiniame ekrane, pasirodys valdiklio konfigūracijos ekranas. Apatiniame kairiajame kampe pamatysite spalvotą kvadratą, tiesiog paspauskite, kad pasirinktumėte naują spalvą. Galite naudoti slankiklį, kad galėtumėte koreguoti pagrindinę spalvą
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ Ši programa naudoja trečiųjų šalių licensijas ir padaro mano gyvenimą geresnį. Ačiū.
+ Trečiųjų šalių licensijos
+ Kotlin (Programavimo kalba)
+ Subsampling Scale Image View (Atvaizdų didinimui)
+ Glide (Atvaizdų įkėlimui ir talpinimui)
+ Picasso (Atvaizdo įkėlimui ir talpinimui)
+ Android Image Cropper (Atvaizdo apkarpimui ir pasukimui)
+ RecyclerView MultiSelect (Keleto elementų pasirinkimui)
+ RtlViewPager (Puslapių braukimui)
+ Joda-Time (Java duomenų pakeitimui)
+ Stetho (Duomenų bazių šifravimui)
+ Otto (Įvykių sąrašams)
+ PhotoView (judančių paveiksliukų didinimui)
+ PatternLockView (modelio apsaugai)
+ Reprint (piršto atspaudo apsaugai)
+ Gif Drawable (GIF\'ų įkėlimui)
+ AutoFitTextView (teksto dydžio keitimui)
+ Robolectric (testavimo aplinkai)
+ Espresso (testavimo pagalbai)
+ Gson (JSON analizatoriui)
+ Leak Canary (atminties nutekėjimo ieškikliui)
+ Number Picker (tinkinamam numerių parinkikliui)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-nb/strings.xml b/commons/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..a9e0f27
--- /dev/null
+++ b/commons/src/main/res/values-nb/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Avbryt
+ Lagre som
+ Fil vellykket lagret
+ Ugyldig filformat
+ Feil: Tomt for minne
+ En feil oppstod: %s
+ Åpne med
+ Ingen gyldig app funnet
+ Sett som
+ Verdi kopiert til utklippstavle
+ Ukjent
+ Alltid
+ Aldri
+ Detaljer
+ Notes
+ Sletter mappen \'%s\'
+ Ingen
+ Label
+
+
+ Favoritter
+ Legg til favoritter
+ Legg til i favorittene
+ Fjern fra favorittene
+
+
+ Søk
+ Skriv inn minst 2 tegn for å starte søket.
+
+
+ Filter
+ Ingen elementer funnet.
+ Endre filter
+
+
+ Tillatelse for lagring kreves
+ Tillatelse for kontakter kreves
+ Tillatelse for kamera kreves
+ Tillatelse for lyd kreves
+
+
+ Endre navn på fil
+ Endre navn på mappe
+ Could not rename the file
+ Could not rename the folder
+ Folder name must not be empty
+ A folder with that name already exists
+ Cannot rename the root folder of a storage
+ Folder renamed successfully
+ Renaming folder
+ Filename cannot be empty
+ Filename contains invalid characters
+ Filename \'%s\' contains invalid characters
+ Extension cannot be empty
+ Source file %s doesn\'t exist
+
+
+ Kopier
+ Flytt
+ Kopier / Flytt
+ Kopier til
+ Flytt til
+ Kilde
+ Mål
+ Velg mål
+ Trykk her for å velge mål
+ Kunne ikke skrive til det valgte målet
+ Velg et mål
+ Kilde og mål kan ikke være det samme
+ Kunne ikke kopiere filene
+ Kopierer…
+ Filer ble kopiert vellykket
+ En feil oppstod
+ Flytter…
+ Filer ble flyttet vellykket
+ Noen filer kunne ikke flyttes
+ Noen filer kunne ikke kopieres
+ Ingen filer valgt
+ Lagrer…
+ Kunne ikke opprette mappen %s
+ Kunne ikke opprette filen %s
+
+
+ Opprett ny
+ Mappe
+ Fil
+ Opprett ny mappe
+ En fil eller mappe med det navnet finnes allerede
+ Navnet inneholder ugyldige tegn
+ Oppfør et navn
+ En ukjent feil oppstod
+
+
+ Filen \"%s\" finnes allerede
+ Filen \"%s\" finnes allerede. Overskrive?
+ Mappen \"%s\" finnes allerede
+ Sammenslå
+ Overskriv
+ Hopp over
+ Tilføy \'_1\'
+ Bruk på alle konflikter
+
+
+ Velg en mappe
+ Velg en fil
+ Bekreft ekstern lagringstilgang
+ Velg rotmappen til SD-kortet på den neste skjermen for å gi skrivetilgang
+ Hvis du ikke ser SD-kortet, prøv dette
+ Bekreft valg
+
+
+ - %d element
+ - %d elementer
+
+
+
+
+ - %d element
+ - %d elementer
+
+
+
+ - Sletter %d element
+ - Sletter %d elementer
+
+
+
+ Velg lagring
+ Intern
+ SD-kort
+ Rot
+ Feil mappe er valgt, velg rotmappen til SD-kortet
+ Bane for SD-kort og OTG-enhet kan ikke være den samme
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Egenskaper
+ Bane
+ Valgte elementer
+ Antall direkte underliggende elementer
+ Totale antall filer
+ Oppløsning
+ Varighet
+ Artist
+ Album
+ Brennvidde
+ Eksponeringstid
+ ISO-verdi
+ Blendertall
+ Kamera
+ EXIF
+ Sangtittel
+
+
+ Bakgrunnsfarge
+ Tekstfarge
+ Primærfarge
+ Forgrunnsfarge
+ Farge for app-ikon
+ Gjenopprett standard
+ Endre farge
+ Tema
+ Endring av farge vil gjøre at det skiftes til Tilpasset tema
+ Lagre
+ Forkast
+ Angre endringer
+ Er du sikker på at du vil angre dine endringer?
+ Du har endringer som ikke er lagret. Lagre før du avslutter?
+ Bruk fargene i alle Simple Apps
+ Fargene er vellykket oppdatert. Et nytt tema kalt \'Delt\' er lagt til, bruk det for å oppdatere alle appenes farger i fremtiden.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Lyst
+ Mørkt
+ Solarisert
+ Mørkerødt
+ Svart & hvitt
+ Tilpasset
+ Delt
+
+
+ Hva er nytt
+ * bare de større oppdateringene er oppført her, det er alltid noen mindre forbedringer også
+
+
+ Slett
+ Fjern
+ Endre navn
+ Del
+ Del via
+ Velg alle
+ Skjul
+ Ikke skjul
+ Skjul mappe
+ Ikke skjul mappe
+ Vis skjulte midlertidig
+ Stopp å vise skjulte media
+ Du kan ikke dele så mye innhold på en gang
+ Tøm og deaktiver papirkurven
+ Angre
+ Annuler angre
+
+
+ Sorter etter
+ Navn
+ Størrelse
+ Sist endret
+ Dato tatt
+ Tittel
+ Filnavn
+ Filendelse
+ Stigende
+ Fallende
+ Bruk kun for denne mappen
+
+
+ Er du sikker på at du vil fortsette med slettingen?
+ Er du sikker på at du vil slette %s?
+ Er du sikker på at du vil flytte %s til papirkurven?
+ Er du sikker på at du vil slette dette elementet?
+ Er du sikker på at du vil flytte dette elementet til papirkurven?
+ Ikke spør igjen i denne økten
+ Ja
+ Nei
+
+
+ - ADVARSEL: Du sletter en mappe
+ - ADVARSEL: Du sletter %d mapper
+
+
+
+ PIN-kode
+ Oppfør PIN-kode
+ Oppfør en PIN-kode
+ Feil PIN-kode
+ Gjenta PIN-kode
+ Mønster
+ Tegn mønster
+ Feil mønster
+ Gjenta mønster
+ Fingeravtrykk
+ Legg til fingeravtrykk
+ Plasser fingeren på fingeravtrykksensoren
+ Autentisering feilet
+ Autentisering blokkert, prøv igjen
+ Du har ingen fingeravtrykk registrert, legg til i enhetens innstillinger
+ Gå til Innstillinger
+ Passord er stilt inn. Installer appen på nytt hvis du glemmer det.
+ Beskyttelse er stilt inn. Installer appen på nytt i tilfelle problemer med å tilbakestille.
+
+
+ I går
+ I dag
+ I morgen
+ sekunder
+ minutter
+ timer
+ dager
+
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %d minutt
+ - %d minutter
+
+
+ - %d time
+ - %d timer
+
+
+ - %d dag
+ - %d dager
+
+
+ - %d uke
+ - %d uker
+
+
+ - %d måned
+ - %d måneder
+
+
+ - %d år
+ - %d år
+
+
+
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %d minutt
+ - %d minutter
+
+
+ - %d time
+ - %d timer
+
+
+
+
+ - %d sekund før
+ - %d sekunder før
+
+
+ - %d minutt før
+ - %d minutter før
+
+
+ - %d time før
+ - %d timer før
+
+
+ - %d dag før
+ - %d dager før
+
+
+
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %d minutt
+ - %d minutter
+
+
+ - %d time
+ - %d timer
+
+
+
+ Tid igjen til alarmen går:\n%s
+ Tid som gjenstår til påminnelsen utløser:\n%s
+ Sørg for at alarmen fungerer ordentlig før du stoler på den. Den kan fungere dårlig på grunn av systembegrensninger relatert til batterisparing.
+ Sørg for at påminnelsene fungerer riktig før du stoler på dem. De kan fungere dårlig på grunn av systembegrensninger relatert til batterisparing.
+
+
+ Alarm
+ Snooze
+ Avvis
+ Ingen påminnelse
+ Ved start
+ Systemlyder
+ Dine lyder
+ Legg til en ny lyd
+ Ingen lyd
+
+
+ Innstillinger
+ Purchase Simple Thank You
+ Tilpass farger
+ Tilpass modulfarger
+ Bruk engelsk språk
+ Ikke vis \'Hva er nytt\' ved oppstart
+ Vis skjulte elementer
+ Skriftstørrelse
+ Liten
+ Middels
+ Stor
+ Ekstra stor
+ Passordbeskytt synlighet for skjulte elementer
+ Passordbeskytt hele appen
+ Behold gammel Sist endret-verdi ved kopier/flytt/endre navn for filer
+ Vis en info-boble ved å dra i rullefeltet
+ Forhindre at telefonen går i hvilemodus mens appen er i forgrunnen
+ Hopp alltid over dialog for slettebekreftelse
+ Aktiver vertikal sveipebevegelse fra toppen for innholdsgjenoppfrisking
+ Bruk 24-timers tidsformat
+ Start uken på søndag
+ Moduler
+ Bruk alltid samme intervall for Snooze
+ Intervall for Snooze
+ Vibrer ved trykk på knapper
+ Flytt elementer til papirkurven istedenfor å slette dem
+ Tømmeintervall for papirkurven
+ Tøm papirkurven
+ Påtving portrettmodus
+
+
+ Synlighet
+ Sikkerhet
+ Rulling
+ Filoperasjoner
+ Papirkurv
+ Saving
+ Startup
+ Text
+
+
+ Gjenopprett denne filen
+ Gjenopprett valgte filer
+ Gjenopprett alle filer
+ Papirkurven er tømt
+ Filer er vellykket gjenopprettet
+ Er du sikker på at du vil tømme papirkurven? Filene vil bli tapt permanent.
+ Papirkurven er tom
+
+
+ Importerer…
+ Eksporterer…
+ Importering vellykket
+ Eksportering vellykket
+ Importering feilet
+ Eksportering feilet
+ Importering av noen oppføringer feilet
+ Eksportering av noen oppføringer feilet
+ Ingen oppføringer for eksportering er funnet
+
+
+ OTG
+ Velg rotmappen til OTG-enheten på den neste skjermen for å gi tilgang
+ Feil mappe valgt, velg rotmappen til OTG-enheten din
+
+
+ Januar
+ Februar
+ Mars
+ April
+ Mai
+ Juni
+ Juli
+ August
+ September
+ Oktober
+ November
+ Desember
+
+
+ i januar
+ i februar
+ i mars
+ i april
+ i mai
+ i juni
+ i juli
+ i august
+ i september
+ i oktober
+ i november
+ i desember
+
+ Mandag
+ Tirsdag
+ Onsdag
+ Torsdag
+ Fredag
+ Lørdag
+ Søndag
+
+ M
+ T
+ O
+ T
+ F
+ L
+ S
+
+ Man
+ Tir
+ Ons
+ Tor
+ Fre
+ Lør
+ Søn
+
+
+ Om
+ For kildekoden besøk
+ Send tilbakemelding eller forslag til
+ Flere apper
+ Tredjepartslisenser
+ Inviter venner
+ Hey, come check out %1$s at %2$s
+ Inviter via
+ Bedøm oss
+ Gi et bidrag
+ Gi et bidrag
+ Følg oss
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Tilleggsinformasjon
+ App-versjon: %s
+ Operativsystem: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Kjøp
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Ofte stilte spørsmål
+ Før du stiller et spørsmål, les først
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ This app uses the following third party libraries to make my life easier. Thank you.
+ Tredjepartslisenser
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-nl/strings.xml b/commons/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..52841fd
--- /dev/null
+++ b/commons/src/main/res/values-nl/strings.xml
@@ -0,0 +1,560 @@
+
+ OK
+ Annuleren
+ Opslaan als
+ Bestand opgeslagen
+ Ongeldig bestandsformaat
+ Onvoldoende geheugen
+ Fout opgetreden: %s
+ Openen met
+ Geen app gevonden om dit bestand mee te openen
+ Instellen als
+ Gekopieerd naar klembord
+ Onbekend
+ Altijd
+ Nooit
+ Details
+ Notities
+ Map \'%s\' verwijderen
+ Geen
+ Label
+
+
+ Favorieten
+ Favorieten toevoegen
+ Aan favorieten toevoegen
+ Uit favorieten verwijderen
+
+
+ Zoeken
+ Vul tenminste 2 tekens in.
+
+
+ Filter
+ Geen items gevonden.
+ Filter aanpassen
+
+
+ Machtiging voor opslag benodigd
+ Machtiging voor contacten benodigd
+ Machtiging voor camera benodigd
+ Machtiging voor geluid benodigd
+
+
+ Bestand hernoemen
+ Map hernoemen
+ Bestandsnaam kon niet worden gewijzigd
+ Mapnaam kon niet worden gewijzigd
+ Mapnaam mag niet leeg zijn
+ Map met deze naam bestaat reeds
+ De naam van een opslagmedium kan niet worden veranderd
+ Naam gewijzigd
+ Map hernoemen
+ Bestandsnaam mag niet leeg zijn
+ Bestandsnaam bevat ongeldige tekens
+ Bestandsnaam \'%s\' bevat ongeldige tekens
+ Extensie mag niet leeg zijn
+ Bronbestand %s bestaat niet
+
+
+ Kopiëren
+ Verplaatsen
+ Kopiëren / Verplaatsen
+ Kopiëren naar
+ Verplaatsen naar
+ Van
+ Naar
+ Kies een bestemming
+ Klik hier om bestemming te kiezen
+ Kon niet naar bestemming schrijven
+ Kies een bestemming
+ Bron en bestemming kunnen niet gelijk zijn
+ Kon de bestanden niet kopiëren
+ Kopiëren…
+ Bestanden gekopieerd
+ Fout opgetreden
+ Verplaatsen…
+ Bestanden verplaatst
+ Sommige bestanden konden niet worden verplaatst
+ Sommige bestanden konden niet worden gekopieerd
+ Geen bestanden geselecteerd
+ Opslaan…
+ Kon de map %s niet aanmaken
+ Kon het bestand %s niet aanmaken
+
+
+ Nieuw
+ Map
+ Bestand
+ Nieuwe map maken
+ Bestand of map met deze naam bestaat reeds
+ Naam bevat ongeldige tekens
+ Voer een naam in
+ Onbekende fout opgetreden
+
+
+ Bestand \"%s\" bestaat reeds
+ Bestand \"%s\" bestaat reeds. Overschrijven?
+ Map \"%s\" bestaat reeds
+ Samenvoegen
+ Overschrijven
+ Overslaan
+ Voeg \'_1\' toe
+ Voor alle conflicterende items
+
+
+ Kies een map
+ Kies een bestand
+ Toegang tot externe opslag bevestigen
+ Kies in het volgende scherm de hoofdmap van de SD-kaart om schrijfrechten te verlenen
+ Probeer het volgende indien de SD-kaart niet in de lijst staat
+ Keuze bevestigen
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - %d item verwijderen
+ - %d items verwijderen
+
+
+
+ Opslag kiezen
+ Interne opslag
+ SD-kaart
+ Hoofdmap
+ Verkeerde map: kies de hoofdmap van de SD-kaart
+ Paden van SD-kaart en OTG-apparaat kunnen niet gelijk zijn
+ Het lijkt erop dat de app is geïnstalleerd op een SD-kaart. Dit zorgt ervoor dat Android de widgets van deze app blokkeert.
+ Dit is een beperking van het systeem en alleen het verplaatsen van de app naar het interne geheugen zal de widgets beschikbaar maken.
+
+
+ Eigenschappen
+ Pad
+ Items geselecteerd
+ Direct onderliggende items
+ Totaal aantal bestanden
+ Resolutie
+ Duur
+ Artiest
+ Album
+ Brandpuntsafstand
+ Sluitertijd
+ ISO-waarde
+ Diafragmagetal
+ Camera
+ EXIF
+ Titel
+
+
+ Achtergrondkleur
+ Tekstkleur
+ Primaire kleur
+ Kleur voorgrond
+ Kleur app-icoon
+ Standaardwaarden
+ Kleur veranderen
+ Thema
+ Zodra een kleur veranderd wordt zal het aangepaste thema worden gebruikt
+ Opslaan
+ Negeren
+ Ongedaan maken
+ Wijzigingen ongedaan maken?
+ Onopgeslagen wijzigingen. Nu opslaan?
+ Kleuren toepassen op al onze apps
+ Kleuren gewijzigd. Gebruik het nieuwe thema \'Gedeeld\' om de kleuren voor al onze apps aan te passen.
+
+ Simple Thank You aanschaffen. Alvast bedankt!
+ ]]>
+
+
+
+ Licht
+ Donker
+ Overbelicht
+ Donkerrood
+ Zwart & Wit
+ Aangepast
+ Gedeeld
+
+
+ Wat is er nieuw
+ * Alleen de belangrijkste veranderingen worden hier vermeld
+
+
+ Verwijderen
+ Weghalen
+ Hernoemen
+ Delen
+ Delen via
+ Alles selecteren
+ Verbergen
+ Tonen
+ Map verbergen
+ Map tonen
+ Verborgen items tonen
+ Verborgen items niet tonen
+ Kan niet zoveel items tegelijk delen
+ Prullenbak leegmaken en uitschakelen
+ Ongedaan maken
+ Opnieuw
+
+
+ Sorteren op
+ Naam
+ Grootte
+ Laatst gewijzigd
+ Datum opname
+ Titel
+ Bestandsnaam
+ Extensie
+ Oplopend
+ Aflopend
+ Alleen voor deze map gebruiken
+
+
+ Deze items verwijderen?
+ %s echt verwijderen?
+ %s echt verplaatsen naar de prullenbak?
+ Item echt verwijderen?
+ Item echt verplaatsen naar de prullenbak?
+ Onthouden voor deze sessie
+ Ja
+ Nee
+
+
+ - WAARSCHUWING: Hele map zal worden verwijderd
+ - WAARSCHUWING: %d mappen zullen worden verwijderd
+
+
+
+ Pincode
+ Voer pincode in
+ Voer een pincode in
+ Verkeerde pincode
+ Herhaal pincode
+ Patroon
+ Teken een patroon
+ Verkeerde patroon
+ Herhaal het patroon
+ Vingerafdruk
+ Voeg vingerafdruk toe
+ Plaats uw vinger op de sensor
+ Authenticatie mislukt
+ Authenticatie geblokkeerd, probeer het op een ander moment
+ Er zijn geen vingerafdrukken geregistreerd. Voeg deze toe via de instellingen van uw toestel.
+ Ga naar Instellingen
+ Wachtwoord ingesteld. Herinstalleer deze app indien u het wachtwoord vergeet.
+ Vingerafdruk beveiliging ingesteld. Herinstalleer deze app indien u niet meer met uw vingerafdruk kunt authenticeren.
+
+
+ Gisteren
+ Vandaag
+ Morgen
+ seconden
+ minuten
+ uren
+ dagen
+
+
+ - %d seconde
+ - %d seconden
+
+
+ - %d minuut
+ - %d minuten
+
+
+ - %d uur
+ - %d uren
+
+
+ - %d dag
+ - %d dagen
+
+
+ - %d week
+ - %d weken
+
+
+ - %d maand
+ - %d maanden
+
+
+ - %d jaar
+ - %d jaren
+
+
+
+
+ - %d seconde
+ - %d seconden
+
+
+ - %d minuut
+ - %d minuten
+
+
+ - %d uur
+ - %d uren
+
+
+
+
+ - %d seconde van tevoren
+ - %d seconden van tevoren
+
+
+ - %d minuut van tevoren
+ - %d minuten van tevoren
+
+
+ - %d uur van tevoren
+ - %d uren van tevoren
+
+
+ - %d dag van tevoren
+ - %d dagen van tevoren
+
+
+
+
+ - %d seconde
+ - %d seconden
+
+
+ - %d minuut
+ - %d minuten
+
+
+ - %d uur
+ - %d uren
+
+
+
+ Het alarm zal afgaan over\n%s
+ De herinnering zal worden getoond over\n%s
+ Controleer eerst of het alarm goed werkt. Er kunnen zich problemen voordoen door de batterijbesparing van Android.
+ Controleer eerst of de herinneringen goed werken. Er kunnen zich problemen voordoen door de batterijbesparing van Android.
+
+
+ Alarm
+ Uitstellen
+ Uitzetten
+ Geen herinnering
+ Als afspraak begint
+ Systeemgeluiden
+ Eigen geluiden
+ Nieuw geluid toevoegen
+ Geen geluid
+
+
+ Instellingen
+ Simple Thank You kopen
+ Kleuren aanpassen
+ Kleuren voor widget aanpassen
+ Use English language
+ \"Wat is er nieuw\" niet tonen
+ Verborgen items tonen
+ Lettergrootte
+ Klein
+ Normaal
+ Groot
+ Extra groot
+ Verborgen items met wachtwoord beveiligen
+ Hele app met wachtwoord beveiligen
+ Datum laatst gewijzigd behouden bij bestandsoperaties
+ Bij slepen van schuifbalk extra informatie tonen
+ Slaapstand voorkomen wanneer de app actief is
+ Niet om bevestiging vragen bij verwijderen
+ Pull-to-refresh inschakelen
+ 24-uursformaat gebruiken
+ Week op zondag beginnen
+ Widgets
+ Altijd dezelfde interval voor uitstellen gebruiken
+ Interval bij uitstellen
+ Trillen bij het indrukken van knoppen
+ Items niet verwijderen, maar verplaatsen naar prullenbak
+ Interval prullenbak leegmaken
+ Prullenbak leegmaken
+ Portretmodus forceren
+
+
+ Opmaak
+ Beveiliging
+ Scrollen
+ Bestandsoperaties
+ Prullenbak
+ Opslaan
+ Opstarten
+ Tekst
+
+
+ Bestand herstellen
+ Selectie herstellen
+ Alles herstellen
+ Prullenbak is geleegd
+ Bestanden zijn hersteld
+ Prullenbak echt leegmaken? De bestanden zullen permanent verwijderd worden.
+ Prullenbak is leeg
+
+
+ Importeren…
+ Exporteren…
+ Importeren voltooid
+ Exporteren voltooid
+ Importeren mislukt
+ Exporteren mislukt
+ Sommige items konden niet worden geïmporteerd
+ Sommige items konden niet worden geëxporteerd
+ Er zijn geen items gevonden om te exporteren
+
+
+ OTG
+ Selecteer in het volgende scherm de hoofdmap van het OTG-apparaat
+ Verkeerde map geselecteerd, kies de hoofdmap van het OTG-apparaat
+
+
+ Januari
+ Februari
+ Maart
+ April
+ Mei
+ Juni
+ Juli
+ Augustus
+ September
+ Oktober
+ November
+ December
+
+
+ in januari
+ in februari
+ in maart
+ in april
+ in mei
+ in juni
+ in juli
+ in augustus
+ in september
+ in oktober
+ in november
+ in december
+
+ Maandag
+ Dinsdag
+ Woensdag
+ Donderdag
+ Vrijdag
+ Zaterdag
+ Zondag
+
+ M
+ D
+ W
+ D
+ V
+ Z
+ Z
+
+ Maa
+ Din
+ Woe
+ Don
+ Vri
+ Zat
+ Zon
+
+
+ Over
+ Bezoek voor de broncode
+ Stuur reacties of suggesties naar
+ Meer apps
+ Licenties van derden
+ Vrienden uitnodigen
+ Hé, bekijk %1$s eens op %2$s
+ Uitnodigen via
+ Beoordeel ons
+ Doneren
+ Doneren
+ Volg ons
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Extra informatie
+ Versie: %s
+ Besturingssysteem: %s
+
+
+ Hopelijk vindt u dit een fijne app. Het bevat geen advertenties, maar u kunt de verdere ontwikkeling wel ondersteunen door de app Simple Thank You aan te schaffen. Dit zal ook voorkomen dat dit venster nog eens verschijnt.
+ Dank u wel!
+ ]]>
+
+ Kopen
+ Er is een nieuwe versie van Simple Thank You
+ Kijk bij problemen eerst naar de Veelgestelde vragen. Wellicht staat de oplossing daar beschreven.
+ Nu lezen
+
+
+
+
+ Er is recentelijk een nieuwe app uitgebracht:
+ %2$s
+ Downloaden kan door een druk op de naam hierboven.
+ Dank u wel!
+ ]]>
+
+
+
+ Veelgestelde vragen
+ Voor het stellen van een vraag, lees eerst de
+ Waarom zie ik de widget van deze app niet in de lijst met widgets?
+ Waarschijnlijk is de app verplaatst naar de SD-kaart. Android verbergt widgets van apps die zich bevinden op de SD-kaart. De enige oplossing is het verplaatsen van de app naar de Interne Opslag via de instellingen van Android.
+ Ik wil een bijdrage leveren, maar ik kan geen geld doneren. Kan ik iets anders doen?
+ Maar natuurlijk! Vertel anderen over deze apps of geef feedback en positieve beoordelingen. Ook het vertalen van de apps in een nieuwe taal of het bijwerken van bestaande vertalingen wordt gewaardeerd.
+ Een handleiding is te vinden op https://github.com/SimpleMobileTools/General-Discussion , of stuur me een bericht via hello@simplemobiletools.com voor hulp.
+ Ik heb per ongeluk bestanden verwijderd, hoe kan ik deze herstellen?
+ Dat gaat helaas niet lukken. Bestanden worden direct na bevestiging verwijderd; er is geen prullenbak.
+ Ik vind de kleuren van de widgets niet mooi, kan ik ze veranderen?
+ Bij het plaatsen van een widget op het startscherm zal er een instellingenvenster verschijnen. Onderaan staan gekleurde vlakken, waarmee nieuwe kleuren kunnen worden gekozen. De schuif is voor de transparantie.
+ Kan ik verwijderde bestanden herstellen?
+ Bestanden worden direct na bevestiging verwijderd, tenzij de prullenbak wordt gebruikt. De prullenbak kan bij de instellingen worden ingeschakeld.
+
+
+ Deze app maakt gebruik van de volgende libraries om het mij wat gemakkelijker te maken. Dank hiervoor.
+ Licenties van derden
+ Kotlin (programmeertaal)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-no/strings.xml b/commons/src/main/res/values-no/strings.xml
new file mode 100644
index 0000000..ad4bc00
--- /dev/null
+++ b/commons/src/main/res/values-no/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Avbryt
+ Lagre som
+ Fil vellykket lagret
+ Ugyldig filformat
+ Out of memory error
+ En feil oppstod: %s
+ Åpne med
+ Ingen gyldig app funnet
+ Sett som
+ Value copied to clipboard
+ Unknown
+ Always
+ Aldri
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Favorites
+ Add favorites
+ Add to favorites
+ Remove from favorites
+
+
+ Søk
+ Type in at least 2 characters to start the search.
+
+
+ Filter
+ No items found.
+ Change filter
+
+
+ Storage permission is required
+ Contacts permission is required
+ Camera permission is required
+ Audio permission is required
+
+
+ Rename file
+ Rename folder
+ Could not rename the file
+ Could not rename the folder
+ Folder name must not be empty
+ A folder with that name already exists
+ Cannot rename the root folder of a storage
+ Folder renamed successfully
+ Renaming folder
+ Filename cannot be empty
+ Filename contains invalid characters
+ Filename \'%s\' contains invalid characters
+ Extension cannot be empty
+ Source file %s doesn\'t exist
+
+
+ Kopier
+ Flytt
+ Kopier / Flytt
+ Kopier til
+ Flytt til
+ Kilde
+ Mål
+ Velg mål
+ Click here to select destination
+ Could not write to the selected destination
+ Please select a destination
+ Source and destination cannot be the same
+ Could not copy the files
+ Copying…
+ Files copied successfully
+ An error occurred
+ Moving…
+ Files moved successfully
+ Some files could not be moved
+ Some files could not be copied
+ No files selected
+ Saving…
+ Could not create folder %s
+ Could not create file %s
+
+
+ Opprett ny
+ Mappe
+ Fil
+ Opprett ny mappe
+ A file or folder with that name already exists
+ The name contains invalid characters
+ Please enter a name
+ An unknown error occurred
+
+
+ File \"%s\" already exists
+ File \"%s\" already exists. Overwrite?
+ Folder \"%s\" already exists
+ Merge
+ Overskriv
+ Hopp over
+ Append with \'_1\'
+ Apply to all
+
+
+ Velg en mappe
+ Velg en fil
+ Confirm external storage access
+ Please choose the root folder of the SD card on the next screen, to grant write access
+ If you don\'t see the SD card, try this
+ Confirm selection
+
+
+ - %d element
+ - %d elementer
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Select storage
+ Internal
+ SD Card
+ Root
+ Wrong folder selected, please select the root folder of your SD card
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Properties
+ Path
+ Items selected
+ Direct children count
+ Total files count
+ Resolution
+ Duration
+ Artist
+ Album
+ Focal length
+ Exposure time
+ ISO speed
+ F-number
+ Camera
+ EXIF
+ Song title
+
+
+ Bakgrunnsfarge
+ Tekstfarge
+ Primærfarge
+ Foreground color
+ App icon color
+ Restore defaults
+ Change color
+ Tema
+ Changing a color will make it switch to Custom theme
+ Lagre
+ Discard
+ Undo changes
+ Are you sure you want to undo your changes?
+ You have unsaved changes. Save before exit?
+ Apply colors to all Simple Apps
+ Colors updated successfully. A new Theme called \'Shared\' has been added, please use that for updating all app colors in the future.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Lyst
+ Mørkt
+ Solarized
+ Dark red
+ Black & White
+ Tilpasset
+ Shared
+
+
+ What\'s new
+ * only the bigger updates are listed here, there are always some smaller improvements too
+
+
+ Slett
+ Remove
+ Endre navn
+ Del
+ Del via
+ Select all
+ Hide
+ Unhide
+ Hide folder
+ Unhide folder
+ Temporarily show hidden
+ Stop showing hidden media
+ You cannot share this much content at once
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sorter etter
+ Navn
+ Størrelse
+ Last modified
+ Date taken
+ Tittel
+ Filnavn
+ Extension
+ Ascending
+ Descending
+ Use for this folder only
+
+
+ Er du sikker på at du vil fortsette med slettingen?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Do not ask again in this session
+ Ja
+ Nei
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Enter PIN
+ Please enter a PIN
+ Wrong PIN
+ Repeat PIN
+ Pattern
+ Insert pattern
+ Wrong pattern
+ Repeat pattern
+ Fingerprint
+ Add fingerprint
+ Please place your finger on the fingerprint sensor
+ Authentication failed
+ Authentication blocked, please try again in a moment
+ You have no fingerprints registered, please add some in the Settings of your device
+ Go to Settings
+ Password setup successfully. Please reinstall the app in case you forget it.
+ Protection setup successfully. Please reinstall the app in case of problems with reseting it.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minutter
+ timer
+ dager
+
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %d minutt
+ - %d minutter
+
+
+ - %d time
+ - %d timer
+
+
+ - %d dag
+ - %d dager
+
+
+ - %d uke
+ - %d uker
+
+
+ - %d måned
+ - %d måneder
+
+
+ - %d år
+ - %d år
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d minutt før
+ - %d minutter før
+
+
+ - %d time før
+ - %d timer før
+
+
+ - %d dag før
+ - %d dager før
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minutt
+ - %d minutter
+
+
+ - %d time
+ - %d timer
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ Ingen påminnelse
+ Ved start
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Innstillinger
+ Purchase Simple Thank You
+ Tilpass farger
+ Customize widget colors
+ Use English language
+ Avoid showing What\'s New on startup
+ Show hidden items
+ Skriftstørrelse
+ Liten
+ Middels
+ Stor
+ Ekstra stor
+ Password protect hidden item visibility
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ File operations
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importerer…
+ Eksporterer…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ Januar
+ Februar
+ Mars
+ April
+ Mai
+ Juni
+ Juli
+ August
+ September
+ Oktober
+ November
+ Desember
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Mandag
+ Tirsdag
+ Onsdag
+ Torsdag
+ Fredag
+ Lørdag
+ Søndag
+
+ M
+ T
+ O
+ T
+ F
+ L
+ S
+
+ Man
+ Tir
+ Ons
+ Tor
+ Fre
+ Lør
+ Søn
+
+
+ Om
+ For the source codes visit
+ Send your feedback or suggestions to
+ More apps
+ Third party licences
+ Invite friends
+ Hey, come check out %1$s at %2$s
+ Invite via
+ Rate us
+ Donate
+ Donate
+ Follow us
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Additional info
+ App version: %s
+ Device OS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ This app uses the following third party libraries to make my life easier. Thank you.
+ Third party licences
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-pl/strings.xml b/commons/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..bb08e4f
--- /dev/null
+++ b/commons/src/main/res/values-pl/strings.xml
@@ -0,0 +1,576 @@
+
+ OK
+ Anuluj
+ Zapisz jako
+ Plik został zapisany
+ Nieprawidłowy format pliku
+ Błąd pamięci
+ Wystąpił błąd: %s
+ Otwórz w
+ Nie mogę znaleźć danych aplikacji
+ Ustaw jako
+ Wartość została skopiowana do schowka
+ Nieznane
+ Zawsze
+ Nigdy
+ Szczegóły
+ Notatnik
+ Usuwam folder \'%s\'
+ None
+ Label
+
+
+ Ulubione
+ Dodaj ulubione
+ Dodaj do ulubionych
+ Usuń z ulubionych
+
+
+ Szukaj
+ Wpisz co najmniej 2 znaki, aby rozpocząć wyszukiwanie.
+
+
+ Filtr
+ Nic nie znalazłem.
+ Zmień filtr
+
+
+ Wymagane jest uprawnienie \'Pamięć\'
+ Wymagane jest uprawnienie \'Kontakty\'
+ Wymagane jest uprawnienie \'Aparat\'
+ Wymagane jest uprawnienie \'Mikrofon\'
+
+
+ Zmień nazwę
+ Zmień nazwę folderu
+ Nie mogę zmienić nazwy pliku
+ Nie mogę zmienić nazwy folderu
+ Nazwa folderu nie może być pusta
+ Folder o tej nazwie już istnieje
+ Nie mogę zmienić nazwy folderu głównego
+ Zmieniono nazwę folderu
+ Zmieniam nazwę folderu…
+ Nazwa pliku nie może być pusta
+ Nazwa pliku zawiera niedozwolone znaki
+ Nazwa \'%s\' zawiera niedozwolone znaki
+ Rozszerzenie pliku nie może być puste
+ Plik źródłowy nie istnieje
+
+
+ Kopiuj
+ Przenieś
+ Kopiuj / przenieś
+ Kopiuj do
+ Przenieś do
+ Żródło
+ Cel
+ Wybierz miejsce docelowe
+ Kliknij tutaj, aby wybrać miejsce docelowe
+ Nie można zapisać w tym miejscu
+ Wybierz miejsce docelowe
+ Miejsce żródłowe i docelowe nie może być takie samo
+ Nie można skopiować pliku
+ Kopiuję…
+ Pliki zostały skopiowane
+ Wystąpił błąd
+ Przenoszę…
+ Pliki zostały przeniesione
+ Niektóre pliki nie mogą być przeniesione
+ Niektóre pliki nie mogą być skopiowane
+ Nie wybrano żadnych plików
+ Zapisuję…
+ Nie mogę utworzyć folderu %s
+ Nie mogę utworzyć pliku %s
+
+
+ Utwórz nowy
+ Folder
+ Plik
+ Utwórz nowy folder
+ Folder lub plik o tej nazwie już istnieje
+ Nazwa zawiera niedozwolone znaki
+ Wpisz nazwę
+ Wystąpił nieznany błąd
+
+
+ Plik \"%s\" już istnieje
+ Plik \"%s\" już istnieje. Nadpisać?
+ Folder \"%s\" już istnieje
+ Połącz
+ Nadpisz
+ Pomiń
+ Dodaj końcówkę \'_1\'
+ Zastosuj dla wszystkich
+
+
+ Wybierz folder
+ Wybierz plik
+ Potwierdź dostęp do karty pamięci
+ Wybierz główny folder na karcie pamięci na następnym ekranie i udziel dostępu do zapisu
+ Jeśli nie widzisz karty pamięci, spróbuj tego:
+ Potwierdź wybór
+
+
+ - %d pozycja
+ - %d pozycje
+ - %d pozycji
+
+
+
+
+ - %d element
+ - %d elementy
+ - %d elementów
+
+
+
+ - Usuwanie %d elementu
+ - Usuwanie %d elementów
+
+
+
+ Wybierz pamięć
+ Wewnętrzna
+ Karta pamięci
+ Folder główny
+ Wybrano nieprawidłowy folder. Wybierz główny folder karty pamięci.
+ Ścieżka do karty pamięci i urządzenia OTG nie może być taka sama
+ Wygląda na to, że aplikacja jest zainstalowana na karcie pamięci. Przez to widżety aplikacji są niedostępne.\nJest to ograniczenie systemu. Jeśli chcesz używać widżetów, musisz przenieść ją do pamięci wewnętrznej.
+
+
+ Właściwości
+ Ścieżka
+ Wybrane pozycje
+ Ilość podfolderów
+ Ilość wszystkich plików
+ Rozdzielczość
+ Czas trwania
+ Artysta
+ Album
+ Długość ogniskowa
+ Czas ekspozycji
+ Czułość ISO
+ Otwór względny obiektywu
+ Kamera/aparat
+ Dane EXIF
+ Tytuł piosenki
+
+
+ Tło
+ Tekst
+ Belka i przełączniki
+ Pierwszy plan
+ Ikona aplikacji
+ Przywróć domyślne
+ Zmień kolor
+ Motyw
+ Zmiana koloru spowoduje przełączenie motywu na własny
+ Zapisz
+ Porzuć
+ Cofnij zmiany
+ Czy na pewno chcesz cofnąć zmiany?
+ Zapisać zmiany?
+ Zastosuj kolory do wszystkich Prostych Aplikacji
+ Kolory zostały zmienione. Utworzony został nowy, \'wspólny\' motyw - użyj go, aby zaktualizować kolorystykę wszystkich Prostych Aplikacji.
+
+ Proste Podziękowanie.
+ ]]>
+
+
+
+ Jasny
+ Ciemny
+ Solaryzacja
+ Ciemnoczerwony
+ Czerń i biel
+ Własny
+ Wspólny
+
+
+ Co nowego
+ * Wymienione są tylko większe zmiany
+
+
+ Usuń
+ Usuń
+ Zmień nazwę
+ Udostępnij
+ Udostępnij w
+ Zaznacz wszystko
+ Ukryj
+ Odkryj
+ Ukryj folder
+ Odkryj folder
+ Tymczasowo pokaż ukryte multimedia
+ Przestań pokazywać ukryte multimedia
+ Nie możesz udostępniać tak wielu danych naraz
+ Wyczyść i wyłącz kosz
+ Cofnij
+ Ponów
+
+
+ Sortuj według parametru:
+ Nazwa
+ Rozmiar
+ Data modyfikacji
+ Data utworzenia
+ Tytuł
+ Nazwa pliku
+ Rozszerzenie
+ Rosnąco
+ Malejąco
+ Tylko w tym folderze
+
+
+ Czy na pewno chcesz to usunąć?
+ Czy na pewno chcesz usunąć %s?
+ Czy na pewno chcesz %s do kosza?
+ Czy na pewno chcesz usunąć ten element?
+ Czy na pewno chcesz przenieść ten element do kosza?
+ Nie pytaj więcej w tej sesji
+ Tak
+ Nie
+
+
+ - UWAGA: usuwasz folder
+ - UWAGA: usuwasz %d foldery
+ - UWAGA: usuwasz %d folderów
+
+
+
+ PIN
+ Wprowadź PIN
+ Wprowadź PIN
+ Nieprawidłowy PIN
+ Wprowadź PIN ponownie
+ Wzór
+ Wprowadź wzór
+ Nieprawidłowy wzór
+ Wprowadź wzór ponownie
+ Odcisk palca
+ Dodaj odcisk palca
+ Przyłóż palec do czytnika
+ Uwierzytelnianie nie powiodło się.
+ Uwierzytelnianie zablokowane. Spróbuj ponownie za chwilę.
+ Nie masz zarejestrowanych odcisków palców. Dodaj je w ustawieniach systemowych.
+ Przejdź do ustawień
+ Hasło zostało ustawione. Jeśli je zapomnisz, przeinstaluj aplikację.
+ Zabezpieczenie zostało ustawione. W razie problemów z jego zresetowaniem przeinstaluj aplikację.
+
+
+ Wczoraj
+ Dzisiaj
+ Jutro
+ sekundy
+ minuty
+ godziny
+ dni
+
+
+ - %d sekunda
+ - %d sekundy
+ - %d sekund
+
+
+ - %d minuta
+ - %d minuty
+ - %d minut
+
+
+ - %d godzina
+ - %d godziny
+ - %d godzin
+
+
+ - %d dzień
+ - %d dni
+
+
+ - %d tydzień
+ - %d tygodnie
+ - %d tygodni
+
+
+ - %d miesiąc
+ - %d miesiące
+ - %d miesięcy
+
+
+ - %d rok
+ - %d lata
+ - %d lat
+
+
+
+
+ - %d sekundę
+ - %d sekundy
+ - %d sekund
+
+
+ - %d minutę
+ - %d minuty
+ - %d minut
+
+
+ - %d godzinę
+ - %d godziny
+ - %d godzin
+
+
+
+
+ - %d sekundę przed
+ - %d sekundy przed
+ - %d sekund przed
+
+
+ - %d minutę przed
+ - %d minuty przed
+ - %d minut przed
+
+
+ - %d godzinę przed
+ - %d godziny przed
+ - %d godzin przed
+
+
+ - %d dzień przed
+ - %d dni przed
+
+
+
+
+ - %d sekundę
+ - %d sekundy
+ - %d sekund
+
+
+ - %d minutę
+ - %d minuty
+ - %d minut
+
+
+ - %d godzinę
+ - %d godziny
+ - %d godzin
+
+
+
+ Czas pozostały do aktywacji alarmu:\n%s
+ Czas pozostały do aktywacji przypomnienia:\n%s
+ Upewnij się, że alarm działa poprawnie zanim zaczniesz na nim polegać. Przez ograniczenia związane m. in. z oszczędzaniem baterii coś może nie wypalić.
+ Upewnij się, że przypomnienia działają poprawnie zanim zaczniesz na nich polegać. Przez ograniczenia związane m. in. z oszczędzaniem baterii coś może nie wypalić.
+
+
+ Alarm
+ Drzemka
+ Wyłącz
+ Brak przypomnienia
+ Przy rozpoczęciu
+ Dźwięki systemowe
+ Dźwięki użytkownika
+ Dodaj nowy dźwięk
+ Brak dźwięku
+
+
+ Ustawienia
+ Kup aplikację \'Proste podziękowanie\'
+ Dostosuj kolory aplikacji
+ Dostosuj kolory widżetu
+ Wymuś używanie przez aplikację języka angielskiego
+ Nie pokazuj okna \'Co nowego\' po uruchomieniu aplikacji
+ Pokazuj ukryte elementy
+ Rozmiar czcionki
+ Mała
+ Średnia
+ Duża
+ Bardzo duża
+ Chroń widoczność ukrytych plików
+ Chroń dostęp do aplikacji
+ Zachowuj starą datę i czas modyfikacji przy kopiowaniu / przenoszeniu / zmianie nazwy plików
+ Pokazuj dodatkowe informacje o elementach przy przeciąganiu paska przewijania
+ Zapobiegaj przejściu urządzenia w stan uśpienia gdy aplikacja jest aktywna
+ Nie pokazuj okna z potwierdzeniem usunięcia elementów
+ Włącz gest pociągnięcia ekranu od góry w celu odświeżenia widoku
+ Używaj 24-godzinnego formatu czasu
+ Rozpoczynaj tydzień od niedzieli
+ Widżety
+ Zawsze używaj tego samego przedziału przypomnienia
+ Długość drzemki
+ Wibracja po wciśnięciu przycisku
+ Przenoś elementy do kosza zamiast je usuwać
+ Przedział czasowy czyszczenia kosza
+ Wyczyść kosz
+ Wymuś tryb pionowy
+
+
+ Widoczność
+ Bezpieczeństwo
+ Przewijanie
+ Operacje na plikach
+ Kosz
+ Zachowywanie
+ Uruchamianie
+ Tekst
+
+
+ Przywróć ten plik
+ Przywróć wybrane pliki
+ Przywróć wszystkie pliki
+ Kosz został opróżniony
+ Pliki zostały przywrócone
+ Czy na pewno chcesz opróżnić kosz? Pliki zostaną bezpowrotnie utracone.
+ Kosz jest pusty
+
+
+ Importuję…
+ Eksportuję…
+ Importowanie zakończone
+ Eksportowanie zakończone
+ Importowanie nie powiodło się
+ Eksportowanie nie powiodło się
+ Importowanie niektórych wpisów nie powiodło się
+ Eksportowanie niektórych wpisów nie powiodło się
+ Nie znalazłem żadnych wpisów do wyeksportowania
+
+
+ OTG
+ Wybierz folder główny urządzenia OTG na następnym ekranie, aby uzyskać dostęp do tegoż urządzenia
+ Wybrany został niewłaściwy folder. Wybierz główny folder urządzenia OTG.
+
+
+ Styczeń
+ Luty
+ Marzec
+ Kwiecień
+ Maj
+ Czerwiec
+ Lipiec
+ Sierpień
+ Wrzesień
+ Październik
+ Listopad
+ Grudzień
+
+
+ w styczniu
+ w lutym
+ w marcu
+ w kwietniu
+ w maju
+ w czerwcu
+ w lipcu
+ w sierpniu
+ we wrześniu
+ w październiku
+ w listopadzie
+ w grudniu
+
+ Poniedziałek
+ Wtorek
+ Środa
+ Czwartek
+ Piątek
+ Sobota
+ Niedziela
+
+ PN
+ W
+ Ś
+ C
+ PT
+ S
+ N
+
+ Pon
+ Wto
+ Śro
+ Czw
+ Pią
+ Sob
+ Nie
+
+
+ O aplikacji
+ Nasza strona internetowa:
+ Napisz do nas, przyślij nam swoje uwagi:
+ Więcej naszych aplikacji
+ Licencje innych firm
+ Zaproponuj aplikację znajomym
+ Hej! Sprawdź aplikację %1$s (%2$s).
+ Wybierz sposób:
+ Oceń aplikację
+ Przekaż datek
+ Przekaż datek
+ Śledź nas
+ Wersja %1$s\n© Simple Mobile Tools %2$d
+ Dodatkowe informacje
+ Wersja aplikacji: %s
+ System: %s
+
+
+ Mamy nadzieję, że aplikacja przypadła Ci do gustu. Jak da się zauważyć, nie zawiera ona reklam, prosimy więc o wsparcie jej rozwoju poprzez zakup specjalnej aplikacji Proste Podziękowanie , co poskutkuje również wyłączeniem tego komunikatu.
+ Dziękujemy!
+ ]]>
+
+ Kup
+ Zaktualizuj aplikcję Proste Podziękowanie do najnowszej wersji
+ Zanim zadasz pytanie, przeczytaj \'Najczęściej zadawane pytania\'. Być może rozwiązanie Twojego problemu już tam jest.
+ Przeczytaj
+
+
+
+
+ Dajemy Ci znać, że ostatnio wyszła nowa aplikacja:
+ %2$s
+ Możesz ją pobrać, klikając powyższy odnośnik.
+ Dziękujemy!
+ ]]>
+
+
+
+ Często zadawane pytania (FAQ)
+ Zanim zadasz pytanie, przeczytaj najpierw
+ Dlaczego nigdzie nie widzę widżetu aplikacji?
+ Zapewne dlatego, że aplikacja została zainstalowana na karcie pamięci. Jedynym rozwiązaniem problemu jest przeniesienie aplikacji do pamięci wewnętrznej. Jest to ograniczenie systemu.
+ Chcę was wesprzeć, ale jestem bez grosza przy duszy. Mogę jakoś to zrobić inaczej?
+ Oczywiście. Możesz powiedzieć światu o naszych aplikacjach, podzielić się opinią i oceną w Sklepie Google Play, lub też wspomóc tłumaczenie aplikacji na nowe języki (bądź poprawić już istniejące tłumaczenia). Instrukcję znajdziesz pod adresem https://github.com/SimpleMobileTools/General-Discussion . Możesz też napisać do nas na adres hello@simplemobiletools.com , jeśli potrzebujesz pomocy.
+ Usunąłem(-am) jakieś pliki przez przypadek. Mogę je jakoś odzyskać?
+ Pliki są usuwane zaraz po kliknięciu w oknie potwierdzającym. System Android nie ma wbudowanego kosza.
+ Nie za bardzo podobają mi się domyślne kolory widżetów. Mogę je jakoś zmienić?
+ Tak. Po przeciągnięciu widżetu na pulpit pojawia się okno jego konfiguracji. Zobaczysz na nim kolorowe kwadraty, za pomocą których możesz wybrać nowe kolory. Suwakiem możesz też ustawić poziom przezroczystości.
+ Czy mogę jakoś odzyskać usunięte pliki?
+ Jeśli zostały faktycznie usunięte, no to nie bardzo (ratunkiem mogą - acz nie muszą - okazać się zewnętrzne aplikacje odzyskujące pliki). Jednakże, możesz włączyć funkcję kosza w ustawieniach niniejszej aplikacji, aby mieć pewność co do ich ewentualnego odzyskania.
+
+
+ Aplikacja korzysta z następujących bibliotek innych firm:
+ Licencje innych firm
+ Kotlin (język programowania)
+ Subsampling Scale Image View (przybliżanie obrazów)
+ Glide (ładowanie obrazów i ich przechowywanie w pamięci podręcznej)
+ Picasso (ładowanie obrazów i ich przechowywanie w pamięci podręcznej)
+ Android Image Cropper (przycinanie i obracanie obrazów)
+ RecyclerView MultiSelect (wybieranie wielu elementów listy)
+ RtlViewPager (przeciąganie ekranu od prawej do lewej strony)
+ Joda-Time (podmienianie daty w języku Java)
+ Stetho (bazy danych debugowania)
+ Otto (szyna zdarzeń)
+ PhotoView (przybliżanie GIFów)
+ PatternLockView (ochrona danych wzorem)
+ Reprint (ochrona danych odciskiem palca)
+ Gif Drawable (ładowanie GIFów)
+ AutoFitTextView (zmiana rozmiaru tekstu)
+ Robolectric (testowanie aplikacji)
+ Espresso (testowanie aplikacji)
+ Gson (parser JSON)
+ Leak Canary (wykrywanie wycieków pamięci)
+ Number Picker (generator liczb losowych)
+ ExoPlayer (odtwarzacz filmów)
+ VR Panorama View (wyświetlanie panoram)
+ Apache Sanselan (odczytywanie metadanych obrazów)
+ Android Photo Filters (fitry obrazów)
+
diff --git a/commons/src/main/res/values-pt-rBR/strings.xml b/commons/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..7fd9678
--- /dev/null
+++ b/commons/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,560 @@
+
+ OK
+ Cancelar
+ Salvar como
+ Arquivo salvo com sucesso
+ Formato de arquivo inválido
+ Memória insuficiente
+ Ocorreu um erro: %s
+ Abrir com
+ Nenhum aplicativo encontrado
+ Definir como
+ Valor copiado para a área de transferência
+ Desconhecido
+ Sempre
+ Nunca
+ Detalhes
+ Notas
+ Excluindo pastas \'%s\'
+ None
+ Label
+
+
+ Favoritos
+ Adicionar favoritos
+ Adicionar aos favoritos
+ Remover dos favoritos
+
+
+ Pesquisar
+ Digite pelo menos 2 letras para iniciar a pesquisa.
+
+
+ Filtrar
+ Nenhum item encontrado.
+ Alterar filtro
+
+
+ É necessária a permissão Armazenamento
+ É necessária a permissão Contatos
+ É necessária a permissão Câmera
+ É necessária a permissão Áudio
+
+
+ Renomear arquivo
+ Renomear pasta
+ Não foi possível renomear o arquivo
+ Não foi possível renomear a pasta
+ O nome da pasta não pode estar vazio
+ Já existe uma pasta com este nome
+ Não se pode renomear a pasta raiz de um armazenamento
+ A pasta foi renomeada com sucesso
+ A renomear pasta
+ O nome do arquivo não pode ficar em branco
+ O nome do arquivo contém caracteres inválidos
+ O nome do arquivo \'%s\' contém caracteres inválidos
+ A extensão não pode ficar em branco
+ Arquivo de origem %s não existe
+
+
+ Copiar
+ Mover
+ Copiar/mover para
+ Copiar para
+ Mover para
+ Origem
+ Destino
+ Selecione o destino
+ Clique aqui para escolher o destino
+ Não foi possível escrever no destino selecionado
+ Por favor selecione um destino
+ A origem e o destino não podem ser iguais
+ Não foi possível copiar os arquivos
+ Copiando…
+ Arquivos copiados com sucesso
+ Ocorreu um erro ao copiar
+ Movendo…
+ Arquivos movidos com sucesso
+ Alguns arquivos não foram movidos
+ Alguns arquivos não foram copiados
+ Nenhum arquivo selecionado
+ Salvando…
+ Não foi possível criar pasta %s
+ Não foi possível criar arquivo %s
+
+
+ Criar
+ Pasta
+ Arquivo
+ Criar nova pasta
+ Já existe um arquivo ou pasta com este nome
+ O nome contém caracteres inválidos
+ Digite um nome
+ Ocorreu um erro desconhecido
+
+
+ Já existe um arquivo com o nome \%s\"
+ Já existe um arquivo com o nome \"%s\". Substituir?
+ A pasta \"%s\" já existe
+ Mesclar
+ Substituir
+ Ignorar
+ Adicionar sufixo \'_1\'
+ Aplicar a todos os conflitos
+
+
+ Selecionar pasta
+ Selecionar arquivo
+ Confirmação de acesso ao armazenamento externo
+ Por favor escolha a pasta raiz do cartão SD no próximo passo para conceder acesso de escrita
+ Se você não encontrar o cartão SD, tente isto
+ Confirmar seleção
+
+
+ - %d item
+ - %d itens
+
+
+
+
+ - %d item
+ - %d itens
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Selecionar armazenamento
+ Interno
+ Cartão SD
+ Raiz
+ Pasta inválida, por favor selecione a raiz do cartão SD
+ Pastas do cartão SD e do dispositivo OTG não podem ser iguais
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Propriedades
+ Caminho
+ Itens selecionados
+ Itens nesta pasta
+ Total de arquivos
+ Resolução
+ Duração
+ Artista
+ Álbum
+ Distância focal
+ Tempo de exposição
+ Velocidade ISO
+ Abertura
+ Câmera
+ EXIF
+ Título da música
+
+
+ Cor de fundo
+ Cor do texto
+ Cor primária
+ Cor da fonte
+ Cor do ícone do aplicativo
+ Restaurar predefinições
+ Mudar cor
+ Tema
+ Mudança nas cores irá alternar para um tema personalizado
+ Salvar
+ Descartar
+ Desfazer alterações
+ Tem certeza que deseja desfazer as alterações?
+ Existem alterações não salvas. Salvar antes de sair?
+ Aplicar cores a todos os Apps Simple
+ Cores atualizadas com sucesso. Um novo tema chamado \'Compartilhado\' foi adicionado, use-o para atualizar as cores de todos os apps no futuro.
+
+ Agradecimento Simple para desbloquear esta função e apoiar o desenvolvimento. Obrigado!
+ ]]>
+
+
+
+ Claro
+ Escuro
+ Ensolarado
+ Vermelho Escuro
+ Preto & branco
+ Personalizado
+ Compartilhado
+
+
+ Novidades
+ * apenas as alterações significativas são listadas aqui, mas existem sempre mais alterações do que as aqui referidas
+
+
+ Apagar
+ Remover
+ Renomear
+ Compartilhar
+ Compartilhar via
+ Selecionar tudo
+ Ocultar
+ Mostrar
+ Ocultar pasta
+ Reexibir pasta
+ Mostrar pastas ocultas temporariamente
+ Parar de exibir mídia oculta
+ Não é possível compartilhar essa quantidade toda de arquivos de uma vez
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Ordenar por
+ Nome
+ Tamanho
+ Data de modificação
+ Data de criação
+ Título
+ Nome do arquivo
+ Extensão
+ Crescente
+ Decrescente
+ Apenas para esta pasta
+
+
+ Deseja realmente excluir?
+ Deseja realmente excluir %s?
+ Deseja realmente mover %s para a Lixeira?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Não perguntar novamente por enquanto
+ Sim
+ Não
+
+
+ - WARNING: Você está excluindo uma pasta
+ - WARNING: Você está excluindo %d pastas
+
+
+
+ PIN
+ Digite o PIN
+ Favor digitar o PIN
+ PIN incorreto
+ Repetir PIN
+ Padrão
+ Inserir padrão
+ Padrão errado
+ Repetir padrão
+ Digital
+ Adicionar digital
+ Posicione seu dedo sobre o leitor de digitais
+ Autenticação falhou
+ Autenticação bloqueada, favor tentar novamente em instantes
+ Você não tem digitais cadastradas. Favor adiconar alguma nas configurações do seu aparelho
+ Ir para configurações
+ Senha criada com sucesso. Reinstale o app caso você a esqueça.
+ Proteção criada com sucesso. Reinstale o app caso tenha problemas em redefinir.
+
+
+ Yesterday
+ Today
+ Amanhã
+ segundos
+ minutos
+ horas
+ dias
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+ - %d dia
+ - %d dias
+
+
+ - %d semana
+ - %d semanas
+
+
+ - %d mês
+ - %d meses
+
+
+ - %d ano
+ - %d anos
+
+
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+
+
+ - %d segundo antes
+ - %d segundos antes
+
+
+ - %d minuto antes
+ - %d minutos antes
+
+
+ - %d hora antes
+ - %d horas antes
+
+
+ - %d dia antes
+ - %d dias antes
+
+
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+
+ Tempo restante até o alarme disparar:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Por favor, verifique se o alarme funciona corretamente antes de confiar nele. Ele pode se comportar mal devido a restrições do sistema relacionadas à economia de bateria.
+ Certifique-se de que os lembretes funcionem corretamente antes de confiar neles. Eles podem se comportar mal devido a restrições do sistema relacionadas à economia de bateria.
+
+
+ Alarme
+ Soneca
+ Dispensar
+ Sem lembrete
+ No início
+ Sons do sistema
+ Seus sons
+ Adicionar um novo som
+ Sem som
+
+
+ Configurações
+ Purchase Simple Thank You
+ Personalizar cores
+ Customize widget colors
+ Usar o idioma inglês
+ Não mostrar novidades das atualizações na inicialização
+ Mostrar itens ocultos
+ Tamanho do texto
+ Pequeno
+ Médio
+ Grande
+ Muito grande
+ Restringir a visualização de itens ocultos com senha
+ Proteção por senha em todo o app
+ Manter valor anterior do arquivo modificado por último ao copiar/mover/renomear arquivo
+ Mostrar bolha de informação ao rolar usando a barra de rolagem
+ Impedir que o telefone durma enquanto o aplicativo está em primeiro plano
+ Sempre pular confirmação de exclusão
+ Habilitar puxar do topo da tela para atualizar o conteúdo
+ Usar formato de tempo 24-horas
+ Iniciar a semana no Domingo
+ Widgets
+ Sempre usar o mesmo tempo de soneca
+ Tempo de soneca
+ Vibrar ao pressionar o botão
+ Mover itens na Lixeira em vez de excluir
+ Intervalo de limpeza da Lixeira
+ Lixeira vazia
+ Forçar modo retrato
+
+
+ Visibilidade
+ Segurança
+ Rolagem
+ Operações de arquivos
+ Lixeira
+ Salvando
+ Inicialização
+ Texto
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importando…
+ Exportando…
+ Importado com sucesso
+ Exportado com sucesso
+ Falha na importação
+ Falha na exportação
+ Houve falha em alguns ítens da importação
+ Houve falha em alguns ítens da exportação
+ Não foram encontramos ítens para exportar
+
+
+ OTG
+ Por favor, escolha a pasta raiz do dispositivo OTG na próxima tela, para conceder acesso
+ Pasta errada selecionada, selecione a pasta raiz do seu dispositivo OTG
+
+
+ janeiro
+ fevereiro
+ março
+ abril
+ maio
+ junho
+ julho
+ agosto
+ setembro
+ outubro
+ novembro
+ dezembro
+
+
+ em Janeiro
+ em Fevereiro
+ em Março
+ em Abril
+ em Maio
+ em Junho
+ em Julho
+ em Agosto
+ em Setembro
+ em Outubro
+ em Novembro
+ em Dezembro
+
+ Segunda-feira
+ Terça-feira
+ Quarta-feira
+ Quinta-feira
+ Sexta-feira
+ Sábado
+ Domingo
+
+ S
+ T
+ Q
+ Q
+ S
+ S
+ D
+
+ Seg
+ Ter
+ Qua
+ Qui
+ Sex
+ Sáb
+ Dom
+
+
+ Sobre
+ Mais aplicativos Simple e código-fonte em
+ Envie os seus comentários ou sugestões para
+ Mais aplicativos
+ Licenças de terceiros
+ Convidar amigos
+ Olá, experimente %1$s em %2$s
+ Convidar via
+ Nos avalie na Play Store
+ Doações
+ Doações
+ Siga-nos
+ V %1$s\nCopyright © Simple Mobile Tools %2$d
+ Informações adicionais
+ Versão do app: %s
+ Sistema operacional: %s
+
+
+ Esperamos que esteja gostando do app. Ele não contém anúncios, então apoie o desenvolvimento adquirindo o Agradecimento ao Simple , ele também bloqueia este diálogo de aparecer.
+ Obrigado!
+ ]]>
+
+ Comprar
+ Por favor, atualize o Simple Thank You para a versão mais recente
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ apenas informando que um novo aplicativo foi lançado recentemente:
+ %2$s
+ Você pode baixá-lo pressionando o título.
+ Obrigado
+ ]]>
+
+
+
+ Perguntas frequentes
+ Antes de fazer uma pergunta, leia primeiro a
+ Por que não vejo esse widget de aplicativos na lista de widgets?
+ É mais provável que você tenha movido o aplicativo para um cartão SD. Existe uma limitação do sistema Android que oculta os widgets de aplicativos fornecidos nesse caso. A única solução é mover o aplicativo de volta para o armazenamento interno por meio das configurações do dispositivo.
+ Eu quero te apoiar, mas não posso doar dinheiro. Há algo mais que eu possa fazer?
+ Sim, claro. Você pode divulgar os aplicativos ou dar um bom feedback e classificação. Você também pode ajudar traduzindo os aplicativos em um novo idioma ou apenas atualizando algumas traduções existentes.
+ Você pode encontrar o guia em https://github.com/SimpleMobileTools/General-Discussion , ou apenas me mande uma mensagem para hello@simplemobiletools.com se precisar de ajuda.
+ Eu apaguei alguns arquivos por engano, como posso recuperá-los?
+ Infelizmente, você não pode. Os arquivos são apagados instantaneamente após a caixa de diálogo de confirmação, não há lixeira disponível.
+ Eu não gosto das cores do widget, posso mudá-las?
+ Sim, enquanto você arrasta um widget em sua tela inicial, uma tela de configuração do widget aparece. Você verá quadrados coloridos no canto inferior esquerdo, basta pressioná-los para escolher uma nova cor. Você pode usar o controle deslizante para ajustar o alfa também.
+ Posso restaurar de alguma forma os arquivos apagados?
+ Se eles foram realmente excluídos, você não pode. No entanto, você pode ativar o uso de uma Lixeira em vez de excluí-la nas configurações do aplicativo. Isso apenas moverá os arquivos, em vez de excluí-los.
+
+
+ Este aplicativo usa as seguintes bibliotecas de terceiros para facilitar a minha vida. Obrigado.
+ Licenças de terceiros
+ Kotlin (linguagem de programação)
+ Subsampling Scale Image View (ampliação de imagens)
+ Glide (carregamento e cache de imagens)
+ Picasso (carregamento e cache de imagens)
+ Android Image Cropper (recorte e rotação de imagens)
+ RecyclerView MultiSelect (seleção múltipla de itens)
+ RtlViewPager (deslizar à direita/esquerda)
+ Joda-Time (sustituto para Java date)
+ Stetho (depuração a bases de dados)
+ Otto (canal de eventos)
+ PhotoView (animação de GIFs)
+ PatternLockView (pattern protection)
+ Reprint (proteção de impressões digitais)
+ Gif Drawable (carregamento de GIFs)
+ AutoFitTextView (redimensionamento de texto)
+ Robolectric (framework de teste)
+ Espresso (helper de teste)
+ Gson (analisador JSON)
+ Leak Canary (detector de vazamento de memória)
+ Number Picker (selecionador personalizável de número)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-pt/strings.xml b/commons/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..fe23341
--- /dev/null
+++ b/commons/src/main/res/values-pt/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Cancelar
+ Guardar como
+ Ficheiro guardado com sucesso
+ Formato de ficheiro inválido
+ Memória insuficiente
+ Ocorreu um erro: %s
+ Abrir com
+ Nenhuma aplicação encontrada
+ Definir como
+ Valor copiado para a área de transferência
+ Desconhecido
+ Sempre
+ Nunca
+ Detalhes
+ Notas
+ A apagar a pasta \'%s\'
+ None
+ Label
+
+
+ Favoritos
+ Add favorites
+ Adicionar aos favoritos
+ Remover dos favoritos
+
+
+ Pesquisar
+ Digite, pelo menos, 2 caracteres para iniciar a pesquisa.
+
+
+ Filtrar
+ Sem itens
+ Alterar filtro
+
+
+ Requer acesso ao armazenamento
+ Requer acesso aos contactos
+ Requer acesso à câmara
+ Requer acesso ao áudio
+
+
+ Renomear ficheiro
+ Renomear pasta
+ Não foi possível renomear o ficheiro
+ Não foi possível renomear a pasta
+ O nome da pasta não pode estar vazio
+ Já existe uma pasta com este nome
+ Não pode renomear a pasta raiz de um armazenamento
+ A pasta foi renomeada com sucesso
+ A renomear pasta
+ O nome do ficheiro não pode estar vazio
+ O nome do ficheiro contém caracteres inválidos
+ O nome \'%s\' contém caracteres inválidos
+ A extensão não pode estar vazia
+ O ficheiro de origem %s não existe
+
+
+ Copiar
+ Mover
+ Copiar/mover para
+ Copiar para
+ Mover para
+ Origem
+ Destino
+ Selecione o destino
+ Toque aqui para escolher o destino
+ Não foi possível escrever no destino selecionado
+ Por favor selecione um destino
+ A origem e o destino não podem ser iguais
+ Não foi possível copiar os ficheiros
+ A copiar…
+ Ficheiros copiados com sucesso
+ Ocorreu um erro ao copiar
+ A mover…
+ Ficheiros movidos com sucesso
+ Alguns ficheiros não foram movidos
+ Alguns ficheiros não foram copiados
+ Nenhum ficheiro selecionado
+ A guardar…
+ Não foi possível criar a pasta %s
+ Não foi possível criar o ficheiro %s
+
+
+ Criar
+ Pasta
+ Ficheiro
+ Criar nova pasta
+ Já existe um ficheiro ou pasta com este nome
+ O nome contém caracteres inválidos
+ Digite um nome
+ Ocorreu um erro desconhecido
+
+
+ Já existe um ficheiro com o nome \"%s\"
+ Já existe um ficheiro com o nome \"%s\". Substituir?
+ Já existe uma pasta com o nome \"%s\"
+ Unir
+ Substituir
+ Ignorar
+ Anexar \'_1\'
+ Aplicar a todos os conflitos
+
+
+ Selecionar pasta
+ Selecionar ficheiro
+ Confirmação de acesso ao armazenamento externo
+ Por favor escolha a pasta raiz do cartão SD no próximo ecrã para conceder acesso de escrita
+ Se não conseguir ver o cartão SD, tente isto
+ Confirmar seleção
+
+
+ - %d item
+ - %d itens
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - A apagar %d item
+ - A apagar %d itens
+
+
+
+ Selecionar armazenamento
+ Interno
+ Cartão SD
+ Raiz
+ Pasta inválida, por favor selecione o cartão SD
+ Não pode utilizar o mesmo caminho para o cartão SD e para o dispositivo OTG
+ Parece que a aplicação está instalada no cartão SD, o que torna o widget indisponível. Nem sequer aparecerá na lista de widgets disponíveis.
+ Esta é uma limitação do sistema e, se quiser usar widgets, tem que mover a aplicação para o armazenamento interno.
+
+
+ Propriedades
+ Caminho
+ Itens selecionados
+ Itens nesta pasta
+ Total de ficheiros
+ Resolução
+ Duração
+ Artista
+ Álbum
+ Distância focal
+ Tempo de exposição
+ Velocidade ISO
+ Número F
+ Câmara
+ EXIF
+ Título da faixa
+
+
+ Cor de fundo
+ Cor do tipo de letra
+ Cor primária
+ Cor secundária
+ Cor do ícone da aplicação
+ Restaurar predefinições
+ Alterar cor
+ Tema
+ Se alterar uma cor, ativa o modo de tema personalizado
+ Guardar
+ Descartar
+ Desfazer alterações
+ Tem a certeza de que deseja desfazer as alterações?
+ Existem alterações não guardadas. Deseja guardar antes de sair?
+ Aplicar cores a todas as aplicações Simple
+ Cores atualizadas com sucesso. Foi criado o novo tema \'Partilhado\', que pode utilizar para atualizar as cores de todas as aplicações Simple.
+
+ Simple Thank You para desbloquear esta funcionalidade e ajudar no desenvolvimento. Obrigado!
+ ]]>
+
+
+
+ Claro
+ Escuro
+ Solar
+ Vermelho escuro
+ Preto e branco
+ Personalizado
+ Partilhado
+
+
+ Novidades
+ * apenas são listadas as alterações mais significativas. Contudo, existem sempre mais alterações do que as aqui referidas.
+
+
+ Apagar
+ Remover
+ Renomear
+ Partilhar
+ Partilhar por
+ Selecionar tudo
+ Ocultar
+ Mostrar
+ Ocultar pasta
+ Mostrar pasta
+ Mostrar ocultas temporariamente
+ Não mostrar multimédia oculta
+ Não pode partilhar tantos itens em simultâneoo
+ Esvaziar e desativar a reciclagem
+ Desfazer
+ Refazer
+
+
+ Ordenar por
+ Nome
+ Tamanho
+ Data de modificação
+ Data de obtenção
+ Título
+ Nome do ficheiro
+ Extensão
+ Ascendente
+ Descendente
+ Apenas para esta pasta
+
+
+ Tem a certeza de que deseja continuar com a eliminação?
+ Tem a certeza de que deseja apagar %s?
+ Tem a certeza de que deseja mover %s para a reciclagem?
+ Tem a certeza de que deseja apagar este item?
+ Tem a certeza de que deseja mover este item para a reciclagem?
+ Não perguntar mais para esta sessão
+ Sim
+ Não
+
+
+ - AVISO: está prestes a apagar uma pasta
+ - AVISO: está prestes a apagar %d pastas
+
+
+
+ PIN
+ Digite o PIN
+ Por favor digite o PIN
+ PIN inválido
+ Repita o PIN
+ Padrão
+ Introduza o padrão
+ Padrão inválido
+ Repita o padrão
+ Impressão digital
+ Adicionar impressão digital
+ Coloque o seu dedo no sensor de impressões digitais
+ Falha de autenticação
+ Autenticação bloqueada. Por favor tente mais tarde.
+ Não tem impressões digitais registadas. Adicione a sua impressão digital nas definições do dispositivo.
+ Ir para Definições
+ Palavra-passe definida com sucesso. Deve reinstalar esta aplicação se não se lembrar da palavra-passe.
+ Proteção definida com sucesso. Deve reinstalar a aplicação se ocorrerem problemas.
+
+
+ Ontem
+ Hoje
+ Amanhã
+ segundos
+ minutos
+ horas
+ dias
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+ - %d dia
+ - %d dias
+
+
+ - %d semana
+ - %d semanas
+
+
+ - %d mês
+ - %d meses
+
+
+ - %d ano
+ - %d anos
+
+
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+
+
+ - %d segundo antes
+ - %d segundos antes
+
+
+ - %d minuto antes
+ - %d minutos antes
+
+
+ - %d hora antes
+ - %d horas antes
+
+
+ - %d dia antes
+ - %d dias antes
+
+
+
+
+ - %d segundo
+ - %d segundos
+
+
+ - %d minuto
+ - %d minutos
+
+
+ - %d hora
+ - %d horas
+
+
+
+ O alarme está definido para daqui a:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Certifique-se de que o alarme está a funcionar corretamente antes de o utilizar na plenitude. Pode ter um comportamento errático devido a restrições do sistema.
+ Certifique-se de que o lembrete está a funcionar corretamente antes de o utilizar na plenitude. Pode ter um comportamento errático devido a restrições do sistema.
+
+
+ Alarme
+ Snooze
+ Descartar
+ Sem lembrete
+ No início
+ Sons do sistema
+ Meus sons
+ Adicionar um novo som
+ Sem som
+
+
+ Definições
+ Purchase Simple Thank You
+ Personalizar cores
+ Personalizar cores do widget
+ Utilizar aplicação em inglês
+ Não mostrar novidades ao iniciar
+ Mostrar itens ocultos
+ Tamanho do texto
+ Pequeno
+ Médio
+ Grande
+ Muito grande
+ Proteger itens ocultos com palavra-passe
+ Proteger aplicação com palavra-passe
+ Manter dados da última modificação ao copiar/mover/renomear
+ Mostrar informação ao arrastar a barra de deslocamento
+ Impedir adormecimento do dispositivo se a aplicação estiver em segundo plano
+ Ignorar sempre a confirmação para eliminação de ficheiros
+ Ativar pull-to-refresh
+ Utilizar formato 24 horas
+ Iniciar semana ao domingo
+ Widgets
+ Usar sempre o mesmo intervalo para snooze
+ Intervalo para snooze
+ Vibrar ao tocar nos botões
+ Mover itens para a reciclagem em vez de os apagar
+ Intervalo de tempo para limpar a reciclagem
+ Limpar reciclagem
+ Impor modo vertical
+
+
+ Visibilidade
+ Segurança
+ Deslocação
+ Operações de ficheiros
+ Reciclagem
+ Guardar
+ Arranque
+ Texto
+
+
+ Restaurar este ficheiro
+ Restarurar os ficheiros selecionados
+ Restaurar todos os ficheiros
+ A reciclagem foi esvaziada
+ Os ficheiros foram restaurados
+ Tem a certeza de que deseja esvaziar a reciclagem? Todos os itens serão apagados permanentemente.
+ A reciclagem está vazia
+
+
+ A importar…
+ A exportar…
+ Importação efetuada
+ Exportação efetuada
+ Falha ao importar
+ Falha ao exportar
+ Falha ao importar alguns itens
+ Falha ao exportar alguns itens
+ Não existem itens para exportação
+
+
+ OTG
+ Escolha a pasta raiz do dispositivo OTG no ecrã seguinte para conceder o acesso
+ Escolheu uma pasta inválida, deve selecionar a pasta raiz do dispositivo OTG
+
+
+ janeiro
+ fevereiro
+ março
+ abril
+ maio
+ junho
+ julho
+ agosto
+ setembro
+ outubro
+ novembro
+ dezembro
+
+
+ em janeiro
+ em fevereiro
+ em março
+ em abril
+ em maio
+ em junho
+ em julho
+ em agosto
+ em setembro
+ em outubro
+ em novembro
+ em dezembro
+
+ segunda
+ terça
+ quarta
+ quinta
+ sexta
+ sábado
+ domingo
+
+ S
+ T
+ Q
+ Q
+ S
+ S
+ D
+
+ seg
+ ter
+ qua
+ qui
+ sex
+ sáb
+ dom
+
+
+ Acerca
+ Mais aplicações Simple e código fonte em
+ Envie os seus comentários ou sugestões para
+ Mais aplicações
+ Licenças de terceiros
+ Convidar amigos
+ Olá, experimente %1$s em %2$s
+ Convidar por
+ Avalie-nos na Play Store
+ Donativos
+ Donativos
+ Siga-nos
+ V %1$s\nCopyright © Simple Mobile Tools %2$d
+ Mais informações
+ Versão da aplicação: %s
+ SO do dispositivo: %s
+
+
+ espero que esteja a gostar desta aplicação. Não possui anúncios e é open source. Se quiser ajudar no desenvolvimento, pode comprar a aplicação Simple Thank You que também impedirá que este diálogo seja novamente mostrado.
+ Obrigado!
+ ]]>
+
+ Comprar
+ por favor atualize a aplicação Simple Thank You para a versão mais recente
+ Antes de colocar uma questão, leia as FAQ pois pode já existir uma solução para o seu problema.
+ Ler
+
+
+
+
+ para que saibas, foi disponibilizada recentemente uma nova aplicação:
+ %2$s
+ Podes descarregar a nova aplicação carregando no título.
+ Obrigado
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ Esta aplicação usa as seguintes bibliotecas de terceiros para facilitar a minha vida. Obrigado.
+ Licenças de terceiros
+ Kotlin (linguagem de programação)
+ Subsampling Scale Image View (ampliação de imagens)
+ Glide (carregamento e cache de imagens)
+ Picasso (carregamento e cache de imagens)
+ Android Image Cropper (recorte e rotação de imagens)
+ RecyclerView MultiSelect (seleção múltipla de itens)
+ RtlViewPager (deslizar à direita/esquerda)
+ Joda-Time (sustituto para Java date)
+ Stetho (depuração a bases de dados)
+ Otto (canal de eventos)
+ PhotoView (animação de GIFs)
+ PatternLockView (proteção com padrões)
+ Reprint (proteção com impressões digitais)
+ Gif Drawable (carregamento de GIFs)
+ AutoFitTextView (redimensionamento de texto)
+ Robolectric (framework de testes)
+ Espresso (apoio aos testes)
+ Gson (processador JSON)
+ Leak Canary (detetor de falhas de memória)
+ Number Picker (seletor de números personalizado)
+ ExoPlayer (reprodutor de vídeo)
+ VR Panorama View (exibição panorâmica)
+ Apache Sanselan (leitor de meta-dados das imagens)
+ Android Photo Filters (filtros de imagens)
+
diff --git a/commons/src/main/res/values-ru/strings.xml b/commons/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..5942660
--- /dev/null
+++ b/commons/src/main/res/values-ru/strings.xml
@@ -0,0 +1,591 @@
+
+ OK
+ Отмена
+ Сохранить как
+ Файл сохранён
+ Недопустимый формат файла
+ Память переполнена
+ Произошла ошибка: %s
+ Открыть в…
+ Приложение не найдено
+ Установить как…
+ Значение скопировано в буфер обмена
+ Неизвестно
+ Всегда
+ Никогда
+ Подробности
+ Заметки
+ Удаление папки \"%s\"
+ Нет
+ Метка
+
+
+ Избранное
+ Добавить избранное
+ Добавить в избранное
+ Убрать из избранного
+
+
+ Поиск
+ Введите как минимум 2 символа для начала поиска.
+
+
+ Фильтрация
+ Ничего не найдено.
+ Изменить фильтрацию
+
+
+ Требуется разрешение для доступа к хранилищу
+ Требуется разрешение для доступа к контактам
+ Требуется разрешение для доступа к камере
+ Требуется разрешение для доступа аудиосистеме
+
+
+ Изменение имени файла
+ Изменение имени папки
+ Не удалось переименовать файл
+ Не удалось переименовать папку
+ Имя папки не должно быть пустым
+ Папка с таким именем уже существует
+ Нельзя переименовать корневую папку
+ Папка переименована
+ Изменение имени папки
+ Имя файла не может быть пустым
+ Имя файла содержит недопустимые символы
+ Имя файла \"%s\" содержит недопустимые символы
+ Расширение не может быть пустым
+ Исходный файл \"%s\" не существует
+
+
+ Копировать
+ Переместить
+ Копировать/переместить
+ Копировать в…
+ Переместить в…
+ Путь источника
+ Путь назначения
+ Выберите путь назначения
+ Нажмите здесь, чтобы выбрать расположение
+ Не удалось записать в выбранное расположение
+ Пожалуйста, выберите путь назначения
+ Исходный и конечный пути не могут совпадать
+ Не удалось скопировать файлы
+ Копирование…
+ Файлы скопированы
+ Произошла ошибка
+ Перемещение…
+ Файлы перемещены
+ Некоторые файлы не могут быть перемещены
+ Некоторые файлы не могут быть скопированы
+ Файлы не выбраны
+ Сохранение…
+ Не удалось создать папку \"%s\"
+ Не удалось создать файл \"%s\"
+
+
+ Создать
+ Папка
+ Файл
+ Новая папка
+ Папка или файл с таким именем уже существует
+ Имя содержит недопустимые символы
+ Пожалуйста, введите имя
+ Произошла неизвестная ошибка
+
+
+ Файл \"%s\" уже существует
+ Файл \"%s\" уже существует. Перезаписать?
+ Папка \"%s\" уже существует
+ Объединить
+ Перезаписать
+ Пропустить
+ Добавить \"_1\" к имени
+ Применить ко всем конфликтам
+
+
+ Выбор папки
+ Выбор файла
+ Предоставление доступа к внешнему накопителю
+ Чтобы предоставить право на запись, пожалуйста, выберите корневую папку SD-карты на следующем шаге
+ Если вы не видите SD-карту, попробуйте это
+ Подтвердить выделение
+
+
+ - %d объект
+ - %d объекта
+ - %d объектов
+
+
+
+
+ - %d объект
+ - %d объекта
+ - %d объектов
+
+
+
+ - Удаление %d объекта
+ - Удаление %d объектов
+ - Удаление %d объектов
+
+
+
+ Выбрать хранилище
+ Внутренняя память
+ SD-карта
+ Корневая папка
+ Папка выбрана неверно, пожалуйста, выберите SD-карту
+ Пути к SD-карте и OTG-устройству не могут быть одинаковыми
+ Похоже, что приложение у вас установлено на SD-карту, а это делает виджеты приложений недоступными. Вы даже не увидите их в списке доступных виджетов.
+ Это системное ограничение, поэтому, если вы хотите использовать виджеты, вам нужно переместить приложение обратно во внутреннюю память.
+
+
+ Свойства
+ Расположение
+ Выбрано элементов
+ Дочерних элементов
+ Всего файлов внутри
+ Разрешение
+ Длительность
+ Исполнитель
+ Альбом
+ Фокусное расстояние
+ Выдержка
+ Светочувствительность (ISO)
+ Относительное отверстие
+ Камера
+ EXIF
+ Название песни
+
+
+ Цвет фона
+ Цвет текста
+ Основной цвет
+ Цвет переднего плана
+ Цвет значка приложения
+ Сбросить к стандартным
+ Изменить цвет
+ Тема
+ Изменение цвета повлечёт переключение на пользовательскую тему.
+ Сохранить
+ Сбросить
+ Отменить изменения
+ Действительно отменить изменения?
+ У вас есть несохранённые изменения. Сохранить перед выходом?
+ Применить цвета ко всем приложениям Simple
+ Цвета обновлены. Добавлена новая тема с названием \"Общая\", в дальнейшем используйте её для обновления цветов всех приложений.
+
+ Simple Thank You, чтобы разблокировать эту функцию и поддержать разработку. Спасибо!
+ ]]>
+
+
+
+ Светлая
+ Тёмная
+ Солнечная
+ Тёмно-красная
+ Чёрно-белая
+ Своя
+ Общая
+
+
+ Что нового
+ * здесь представлены только значительные изменения, но всегда присутствуют и мелкие исправления
+
+
+ Удалить
+ Убрать
+ Переименовать
+ Поделиться
+ Отправить через
+ Выделить все
+ Скрыть
+ Показать
+ Скрыть папку
+ Показать папку
+ Временный показ скрытых
+ Не показывать скрытые медиа
+ Слишком большой объём данных, чтобы делиться им
+ Очистить и отключить корзину
+ Отменить
+ Повторить
+
+
+ Сортировать по…
+ Имя
+ Размер
+ Время изменения
+ Время создания
+ Название
+ Имя файла
+ Расширение
+ По возрастанию
+ По убыванию
+ Использовать только для этой папки
+
+
+ Подтверждение удаления
+ Вы действительно хотите удалить %s?
+ Вы действительно хотите переместить %s в корзину?
+ Вы действительно хотите удалить этот объект?
+ Вы действительно хотите переместить этот объект в корзину?
+ Не спрашивать снова (до следующего запуска)
+ Да
+ Нет
+
+
+ - ПРЕДУПРЕЖДЕНИЕ: вы удаляете папку
+ - ПРЕДУПРЕЖДЕНИЕ: вы удаляете %d папки
+ - ПРЕДУПРЕЖДЕНИЕ: вы удаляете %d папок
+ - ПРЕДУПРЕЖДЕНИЕ: вы удаляете %d папок
+
+
+
+ PIN-код
+ Введите PIN-код
+ Введите PIN-код
+ Неверный PIN-код
+ Повторите PIN-код
+ Графический ключ
+ Установите ключ
+ Неправильный графический ключ
+ Повторите ключ
+ Отпечаток пальца
+ Добавить отпечаток
+ Пожалуйста, поместите палец на датчик отпечатков.
+ Ошибка аутентификации
+ Аутентификация заблокирована, повторите попытку через некоторое время
+ У вас нет зарегистрированных отпечатков пальцев, добавьте их в настройках вашего устройства
+ Перейдите в настройки
+ Пароль установлен. Пожалуйста, переустановите приложение в случае, если вы его забудете.
+ Установка защиты выполнена успешно. Переустановите приложение в случае возникновения проблем с её отключением.
+
+
+ Вчера
+ Сегодня
+ Завтра
+ секунд
+ минут
+ часов
+ дней
+
+
+ - %d секунда
+ - %d секунды
+ - %d секунд
+
+
+ - %d минута
+ - %d минуты
+ - %d минут
+
+
+ - %d час
+ - %d часа
+ - %d часов
+
+
+ - %d день
+ - %d дня
+ - %d дней
+ - %d дня(-ей)
+
+
+ - %d неделя
+ - %d недели
+ - %d недель
+ - %d недели(-ль)
+
+
+ - %d месяц
+ - %d месяца
+ - %d месяцев
+ - %d месяца(-ев)
+
+
+ - %d год
+ - %d года
+ - %d лет
+ - %d года(лет)
+
+
+
+
+ - %d секунду
+ - %d секунды
+ - %d секунд
+
+
+ - %d минуту
+ - %d минуты
+ - %d минут
+
+
+ - %d час
+ - %d часа
+ - %d часов
+
+
+
+
+ - %d секунда до события
+ - %d секунды до события
+ - %d секунд до события
+ - %d секунд(-ы) до события
+
+
+ - %d минута до события
+ - %d минуты до события
+ - %d минут до события
+ - %d минут(-ы) до события
+
+
+ - %d час до события
+ - %d часа до события
+ - %d часов до события
+ - %d часа(-ов) до события
+
+
+ - %d день до события
+ - %d дня до события
+ - %d дней до события
+ - %d дня(-ей) до события
+
+
+
+
+ - %d секунду
+ - %d секунды
+ - %d секунд
+
+
+ - %d минуту
+ - %d минуты
+ - %d минут
+
+
+ - %d час
+ - %d часа
+ - %d часов
+
+
+
+ Время, оставшееся до срабатывания будильника:\n%s
+ Время, оставшееся до срабатывания напоминания:\n%s
+ Пожалуйста, убедитесь, что будильник работает правильно, прежде чем полагаться на него. Он может не работать должным образом из-за системных ограничений, связанных с экономией батареи.
+ Пожалуйста, убедитесь, что напоминания работают правильно, прежде чем полагаться на них. Они могут не работать должным образом из-за системных ограничений, связанных с экономией батареи.
+
+
+ Будильник
+ Отложить
+ Закрыть
+ Без напоминания
+ С наступлением события
+ Системные звуки
+ Свои звуки
+ Добавить новый звук
+ Без звука
+
+
+ Настройки
+ Купить Simple Thank You
+ Настроить цвета
+ Настроить цвета виджета
+ Use English language
+ Не показывать \"Что нового\" при запуске
+ Показывать скрытые папки
+ Размер шрифта
+ Мелкий
+ Нормальный
+ Крупный
+ Очень крупный
+ Защита паролем отображения скрытых объектов
+ Защита паролем всего приложения
+ Сохранять дату изменения файла при копировании/перемещении/переименовании
+ Показывать информационное окно при прокрутке элементов перетаскиванием полосы прокрутки
+ Предотвращать засыпание устройства, пока приложение находится на переднем плане
+ Пропускать диалог подтверждения удаления
+ Использовать \"потянуть-для-обновления\" в верхней части экрана
+ Использовать 24-часовой формат времени
+ Воскресенье — начало недели
+ Виджеты
+ Всегда использовать один и тот же интервал повтора
+ Интервал повтора
+ Вибрация при нажатии кнопок
+ Перемещать объекты в корзину вместо удаления
+ Интервал очистки корзины
+ Очистить корзину
+ Принудительный портретный режим
+
+
+ Отображение
+ Безопасность
+ Прокрутка
+ Файловые операции
+ Корзина
+ Сохранение
+ Запуск
+ Текст
+
+
+ Восстановить этот файл
+ Восстановить выбранные файлы
+ Восстановить все файлы
+ Корзина очищена
+ Файлы восстановлены
+ Вы действительно хотите очистить корзину? Файлы будут удалены безвозвратно.
+ Корзина пуста
+
+
+ Импорт…
+ Экспорт…
+ Импортирование выполнено успешно
+ Экспортирование выполнено успешно
+ Импортирование завершилось неудачей
+ Экспортирование завершилось неудачей
+ Импортирование некоторых элементов завершилось неудачей
+ Экспортирование некоторых элементов завершилось неудачей
+ Нет элементов для экспортирования
+
+
+ OTG
+ Пожалуйста, выберите корневую папку OTG-устройства на следующем экране, чтобы предоставить к нему доступ.
+ Выбрана неправильная папка. Выберите корневую папку вашего OTG-устройства.
+
+
+ Январь
+ Февраль
+ Март
+ Апрель
+ Май
+ Июнь
+ Июль
+ Август
+ Сентябрь
+ Октябрь
+ Ноябрь
+ Декабрь
+
+
+ в январе
+ в феврале
+ в марте
+ в апреле
+ в мае
+ в июне
+ в июле
+ в августе
+ в сентябре
+ в октябре
+ в ноябре
+ в декабре
+
+ Понедельник
+ Вторник
+ Среда
+ Четверг
+ Пятница
+ Суббота
+ Воскресенье
+
+ Пн
+ Вт
+ Ср
+ Чт
+ Пт
+ Сб
+ Вс
+
+ пн
+ вт
+ ср
+ чт
+ пт
+ сб
+ вс
+
+
+ О приложении
+ Исходный код можно найти по адресу
+ Отправить отзывы или предложения
+ Больше простых приложений
+ Лицензии третьих сторон
+ Предложить другу
+ Попробуй %1$s по ссылке %2$s
+ Предложить с помощью
+ Оценить нас в Google Play
+ Поддержать разработчика
+ Поддержать разработчика
+ Подписаться на нас
+ v %1$s\nАвторские права © Simple Mobile Tools %2$d
+ Дополнительная информация
+ Версия приложения: %s
+ ОС устройства: %s
+
+
+ надеюсь, вам нравится приложение. Оно не содержит рекламы, поэтому, пожалуйста, поддержите разработку, купив приложение Simple Thank You . Покупка также предотвратит повторное появление данного диалогового окна.
+ Спасибо!
+ ]]>
+
+ Купить
+ Пожалуйста, обновите \"Simple Thank You\" до последней версии.
+ Прежде чем задать вопрос, пожалуйста, прочтите ответы на часто задаваемые вопросы (FAQ), возможно, решение уже есть.
+ Прочитать
+
+
+
+
+ просто сообщаю, что недавно было выпущено новое приложение:
+ %2$s
+ Вы можете скачать его, нажав на заголовок.
+ Спасибо
+ ]]>
+
+
+
+ Часто задаваемые вопросы
+ Перед тем как задать вопрос, пожалуйста, сначала прочитайте
+ Почему я не вижу виджет этого приложения в списке виджетов?
+ Это, скорее всего, потому, что вы перенесли приложение на SD-карту. Существует ограничение системы Android, которое скрывает виджеты приложения
+ в этом случае. Единственное решение — переместить приложение обратно во внутреннее хранилище через настройки вашего устройства.
+ Я хочу поддержать вас, но могу пожертвовать деньги. Есть ли что-нибудь ещё, что я могу сделать?
+ Да, конечно. Вы можете распространять информацию о приложениях или давать хорошие отзывы и оценки. Также вы можете помочь, переведя приложения на новый язык или просто обновив существующие переводы.
+ Руководство можно найти по адресу https://github.com/SimpleMobileTools/General-Discussion или просто написать мне короткое сообщение на hello@simplemobiletools.com , если вам нужна помощь.
+ Я по ошибке удалил некоторые файлы, как их восстановить?
+ К сожалению, вы не сможете. Файлы удаляются сразу после диалога подтверждения, функции вроде \"корзины\" не предусмотрено.
+ Мне не нравятся цвета виджета, я могу их изменить?
+ Да, при перетаскивании виджета на главный экран появляется панель его настройки. На ней вы увидите цветные квадраты в левом нижнем углу, просто нажмите их, чтобы выбрать новый цвет. Также можно использовать ползунок для настройки прозрачности.
+ Могу я как-то восстановить удалённые файлы?
+ Если они были действительно удалены, то не можете. Однако можно включить использование корзины вместо удаления в настройках приложения. Тогда файлы будут просто перемещаться в неё, а не удаляться.
+
+
+ Это приложение использует следующие библиотеки сторонних разработчиков, чтобы облегчить мой труд. Спасибо.
+ Лицензии третьих сторон
+ Kotlin (язык программирования)
+ Subsampling Scale Image View (возможность масштабирования изображений)
+ Glide (загрузка и кэширование изображений)
+ Picasso (загрузка и кэширование изображений)
+ Android Image Cropper (обрезка и поворот изображений)
+ RecyclerView MultiSelect (выбор нескольких элементов списка)
+ RtlViewPager (отображение с RTL-языками)
+ Joda-Time (замена даты Java)
+ Stetho (отладка баз данных)
+ Otto (шина событий)
+ PhotoView (масштабирование GIF)
+ PatternLockView (защита графическим ключом)
+ Reprint (защита по отпечатку пальца)
+ Gif Drawable (загрузка GIFов)
+ AutoFitTextView (масштабирование текста)
+ Robolectric (фреймворк для тестирования)
+ Espresso (помощник по тестированию)
+ Gson (анализатор JSON)
+ Leak Canary (детектор утечек памяти)
+ Number Picker (настраиваемый ввод чисел)
+ ExoPlayer (видеопроигрыватель)
+ VR Panorama View (отображение панорам)
+ Apache Sanselan (чтение метаданных изображений)
+ Android Photo Filters (фильтры изображений)
+
diff --git a/commons/src/main/res/values-sk/strings.xml b/commons/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..c0df6bf
--- /dev/null
+++ b/commons/src/main/res/values-sk/strings.xml
@@ -0,0 +1,582 @@
+
+ OK
+ Zrušiť
+ Uložiť ako
+ Súbor bol úspešne uložený
+ Neplatný formát súboru
+ Došlo k chybe s nedostatkom pamäte
+ Došlo k chybe: %s
+ Otvoriť pomocou
+ Nenašla sa žiadna vhodná aplikácia
+ Nastaviť ako
+ Hodnota bola vložená do schránky
+ Neznámy
+ Vždy
+ Nikdy
+ Detaily
+ Poznámky
+ Odstraňuje sa priečinok \'%s\'
+ Žiadny
+ Štítok
+
+
+ Obľúbené
+ Pridať obľúbené
+ Pridať medzi obľúbené
+ Odstrániť spomedzi obľúbených
+
+
+ Hľadať
+ Zadajte aspoň 2 znaky pre spustenie hľadania.
+
+
+ Filtrovať
+ Nenašli sa žiadne položky.
+ Upraviť filter
+
+
+ Je potrebné povolenie pre prístup k súborom
+ Je potrebné povolenie pre prístup ku kontaktom
+ Je potrebné povolenie pre prístup ku kamere
+ Je potrebné povolenie pre prístup ku zvuku
+
+
+ Premenovať súbor
+ Premenovať priečinok
+ Nepodarilo sa premenovať súbor
+ Nepodarilo sa premenovať priečinok
+ Názov priečinka nemôže byť prázdny
+ Priečinok s daným názvom už existuje
+ Nepodarilo sa premenovať základný priečinok úložiska
+ Priečinok bol úspešne premenovaný
+ Premenúva sa priečinok
+ Názov súboru nemôže byť prázdny
+ Názov súboru obsahuje neplatné znaky
+ Názov súboru \'%s\' obsahuje neplatné znaky
+ Prípona nesmie byť prázdna
+ Zdrojový súbor %s neexistuje
+
+
+ Kopírovať
+ Presunúť
+ Kopírovať / presunúť
+ Kopírovať do
+ Presunúť do
+ Zdroj
+ Cieľ
+ Vyberte cieľovú cestu
+ Kliknite sem pre zvolenie cieľovej cesty
+ Nepodarilo sa písať na cieľovú polohu
+ Prosím zvoľte cieľ
+ Zdroj a cieľ sa nemôžu zhodovať
+ Nepodarilo sa skopírovať súbory
+ Kopírovanie…
+ Súbory boli úspešne skopírované
+ Došlo k chybe
+ Presúvanie…
+ Súbory boli úspešne presunuté
+ Nepodarilo sa presunúť niektoré súbory
+ Nepodarilo sa skopírovať niektoré súbory
+ Niesú označené žiadne súbory
+ Ukladanie…
+ Nepodarilo sa vytvoriť priečinok %s
+ Nepodarilo sa vytvoriť súbor %s
+
+
+ Vytvoriť nový
+ Adresár
+ Súbor
+ Vytvoriť nový adresár
+ Súbor alebo adresár s daným názvom už existuje
+ Názov obsahuje neplatné znaky
+ Prosím zadajte názov
+ Došlo k neznámej chybe
+
+
+ Súbor \"%s\" už existuje
+ Súbor \"%s\" už existuje. Prepísať?
+ Priečinok \"%s\" už existuje
+ Zlúčiť
+ Prepísať
+ Preskočiť
+ Pripnúť \'_1\'
+ Použiť na všetky konflikty
+
+
+ Zvoľte priečinok
+ Zvoľte súbor
+ Potvrďte prístup k súborom
+ Pre poskytnutie práva na zápis prosím zvoľte na nasledujúcej obrazovke základný priečǐnok SD karty
+ Ak nevidíte SD kartu, skúste toto
+ Potvrdiť výber
+
+
+ - %d položka
+ - %d položky
+ - %d položiek
+
+
+
+
+ - %d položku
+ - %d položky
+ - %d položiek
+
+
+
+ - Odstraňuje sa %d položka
+ - Odstraňujú sa %d položky
+ - Odstraňuje sa %d položiek
+
+
+
+ Zvoľte úložisko
+ Interné
+ SD karta
+ Koreňový priečinok
+ Bol zvolený nesprávny koreňový priečinok, prosím zvoľte SD kartu
+ Cesta k SD karte a OTG zariadeniu nesmie byť rovnaká
+ Vyzerá to tak, že máte aplikáciu nainštalovanú na SD karte, čo spôsobí nedostupnosť widgetov. Ani ich neuvidíte na zozname dostupných widgetov.
+ Ide o systémové obmedzenie, čiže ak chcete widgety používať, musíte presunúť aplikáciu späť na internú pamäť.
+
+
+ Vlastnosti
+ Cesta
+ Počet zvolených položiek
+ Počet priamych podpoložiek
+ Celkový počet vnútorných položiek
+ Rozlíšenie
+ Trvanie
+ Skupina
+ Album
+ Ohnisková vzdialenosť
+ Dĺžka expozície
+ Rýchlosť ISO
+ Clonové číslo
+ Fotoaparát
+ EXIF
+ Názov skladby
+
+
+ Farba pozadia
+ Farba textu
+ Primárna farba
+ Farba popredia
+ Farba spúšťacej ikonky apky
+ Obnoviť predvolené
+ Zmeniť farbu
+ Téma
+ Zmena farby spôsobí prepnutie na Vlastnú tému
+ Uložiť
+ Zahodiť
+ Vrátiť zmeny
+ Ste si istý, že chcete vrátiť zmeny?
+ Máte neuložené zmeny. Chcete ich uložiť pred ukončením?
+ Použiť farby na všetky Jednoduché Aplikácie
+ Farby boli úspešne aktualizované. Bola pridaná nová \'Zdieľaná\' téma, prosím použite ju pri ďalšej úprave farieb.
+
+ Jednoduché ďakujem na odomknutie tejto funkcie a podporu vývoja. Vďaka!
+ ]]>
+
+
+
+ Svetlá
+ Tmavá
+ Solarizovaná
+ Tmavo červená
+ Čiernobiela
+ Vlastná
+ Zdieľaná
+
+
+ Novinky
+ * sú tu vypísané iba väčšie aktualizácie, stále sú vykonané aj menšie opravy
+
+
+ Vymazať
+ Odstrániť
+ Premenovať
+ Zdieľať
+ Zdieľať cez
+ Vybrať všetko
+ Skryť
+ Odkryť
+ Skryť priečinok
+ Odkryť priečinok
+ Dočasne zobraziť skryté
+ Ukončiť zobrazovanie skrytých médií
+ Nemôžete zdieľať toľko obsahu naraz
+ Vysypať a deaktivovať odpadkový kôš
+ Späť
+ Obnoviť
+
+
+ Na zoradenie použiť
+ Názov
+ Veľkosť
+ Dátum poslednej úpravy
+ Dátum vytvorenia
+ Názov
+ Názov súboru
+ Prípona
+ Vzostupne
+ Klesajúco
+ Použiť iba pre tento priečinok
+
+
+ Ste si istý, že chcete pokračovať s vymazaním?
+ Ste si istý, že chcete vymazať %s?
+ Ste si istý, že chcete vyhodiť %s do koša?
+ Ste si istý, že chcete vymazať túto položku?
+ Ste si istý, že chcete vyhodiť túto položku do odpadkového koša?
+ Nepýtať sa už v tomto spustení
+ Áno
+ Nie
+
+
+ - UPOZORNENIE: Chystáte sa vymazať priečinok
+ - UPOZORNENIE: Chystáte sa vymazať %d priečinky
+ - UPOZORNENIE: Chystáte sa vymazať %d priečinkov
+
+
+
+ PIN
+ Zadajte PIN
+ Prosím zadajte PIN
+ Nesprávny PIN
+ Zopakujte PIN
+ Vzor
+ Zadajte vzor
+ Nesprávny vzor
+ Zopakujte vzor
+ Odtlačok
+ Pridať odtlačok prsta
+ Prosím priložte prst ku senzoru odtlačku prsta
+ Overovanie zlyhalo
+ Overovanie je zablokované, prosím skúste o chvíľu
+ Nemáte zaregistrované žiadne odtlačky, prosím pridajte nejaké v Nastaveniach zariadenia
+ Ísť do nastavení
+ Heslo bolo úspešne nastavené. Ak ho zabudnete, prosím preinštalujte aplikáciu.
+ Ochrana bola úspešne nastavená. Prosím preinštalujte aplikáciu v prípade problémov s jej zmenou.
+
+
+ Včera
+ Dnes
+ Zajtra
+ sekundy
+ minúty
+ hodiny
+ dni
+
+
+ - %d sekunda
+ - %d sekundy
+ - %d sekúnd
+
+
+ - %d minúta
+ - %d minúty
+ - %d minút
+
+
+ - %d hodina
+ - %d hodiny
+ - %d hodín
+
+
+ - %d deň
+ - %d dni
+ - %d dní
+
+
+ - %d týždeň
+ - %d týždne
+ - %d týždňov
+
+
+ - %d mesiac
+ - %d mesiace
+ - %d mesiacov
+
+
+ - %d rok
+ - %d roky
+ - %d rokov
+
+
+
+
+ - %d sekundu
+ - %d sekundy
+ - %d sekúnd
+
+
+ - %d minútu
+ - %d minúty
+ - %d minút
+
+
+ - %d hodinu
+ - %d hodiny
+ - %d hodín
+
+
+
+
+ - %d sekundu vopred
+ - %d sekundy vopred
+ - %d sekúnd vopred
+
+
+ - %d minútu vopred
+ - %d minúty vopred
+ - %d minút vopred
+
+
+ - %d hodinu vopred
+ - %d hodiny vopred
+ - %d hodín vopred
+
+
+ - %d deň vopred
+ - %d dni vopred
+ - %d dní vopred
+
+
+
+
+ - %d sekundu
+ - %d sekundy
+ - %d sekúnd
+
+
+ - %d minútu
+ - %d minúty
+ - %d minút
+
+
+ - %d hodinu
+ - %d hodiny
+ - %d hodín
+
+
+
+ Zostávajúci čas do zvonenia budíka:\n%s
+ Zostávajúci čas do pripomienky:\n%s
+ Pred tým, ako by ste sa na budík spoliehali, sa prosím uistite, že funguje správne. Niektoré systémové obmedzenia spojené so zlepšením výdrže batérie môžu spôsobiť problémy.
+ Pred tým, ako by ste sa na pripomienky spoliehali, sa prosím uistite, že fungujú správne. Niektoré systémové obmedzenia spojené so zlepšením výdrže batérie môžu spôsobiť problémy.
+
+
+ Budík
+ Odložiť
+ Odstrániť
+ Žiadna pripomienka
+ Na začiatku
+ Systémové zvuky
+ Vaše zvuky
+ Pridať nový zvuk
+ Žiadny zvuk
+
+
+ Nastavenia
+ Kúpiť Jednoduché Ďakujem
+ Upraviť farby
+ Upraviť farby widgetov
+ Použiť angličtinu
+ Nikdy nezobrazovať Novinky po spustení
+ Zobraziť skryté položky
+ Veľkosť písma
+ Malé
+ Stredné
+ Veľké
+ Extra veľké
+ Uzamknúť viditeľnosť skrytých položiek heslom
+ Uzamknúť heslom celú aplikáciu
+ Ponechať starú hodnotu naposledy-upravené pri kopírovaní/presúvaní/premenúvaní súborov
+ Zobraziť informačnú bublinu pri prehliadaní položiek ťahaním posuvníka
+ Zabrániť uspaniu zariadenia kým je apka v popredí
+ Stále preskočiť potvrdenie vymazania súborov
+ Povoliť obnovenie súborov potiahnutím zhora
+ Použiť 24 hodinový časový formát
+ Začať týždeň nedeľou
+ Widgety
+ Použiť stále rovnaký čas s Odložiť
+ Čas odloženia
+ Vibrovať pri stlačení tlačidiel
+ Presunúť položky miesto vymazania do odpadkového koša
+ Interval vysypávania koša
+ Vysypať odpadkový kôš
+ Stále použiť režim na výšku
+
+
+ Viditeľnosť
+ Bezpečnosť
+ Skrolovanie
+ Súborové operácie
+ Odpadkový kôš
+ Ukladanie
+ Po spustení
+ Text
+
+
+ Obnoviť tento súbor
+ Obnoviť vybrané súbory
+ Obnoviť všetky súbory
+ Odpadkový kôš bol vyprázdnený
+ Súbory boli úspešne obnovené
+ Ste si istý, že chcete vysypať odpadkový kôš? Súbory budú navždy stratené.
+ Odpadkový kôš je prázdny
+
+
+ Importovanie…
+ Exportovanie…
+ Importovanie bolo úspešné
+ Exportovanie bolo úspešné
+ Importovanie zlyhalo
+ Exportovanie zlyhalo
+ Importovanie niektorých položiek zlyhalo
+ Exportovanie niektorých položiek zlyhalo
+ Nenašli sa žiadne položky pre export
+
+
+ OTG
+ Pre poskytnutie práv prosím zvoľte na nasledujúcej obrazovke základný priečinok OTG zariadenia
+ Bol zvolený nesprávny koreňový priečinok, prosím zvoľte OTG zariadenie
+
+
+ Január
+ Február
+ Marec
+ Apríl
+ Máj
+ Jún
+ Júl
+ August
+ September
+ Október
+ November
+ December
+
+
+ v januári
+ vo februári
+ v marci
+ v apríli
+ v máji
+ v júni
+ v júli
+ v auguste
+ v septembri
+ v októbri
+ v novembri
+ v decembri
+
+ Pondelok
+ Utorok
+ Streda
+ Štvrtok
+ Piatok
+ Sobota
+ Nedeľa
+
+ P
+ U
+ S
+ Š
+ P
+ S
+ N
+
+ Po
+ Ut
+ St
+ Št
+ Pi
+ So
+ Ne
+
+
+ O aplikácií
+ Pre zdrojové kódy navštívte
+ Odozvu a návrhy posielajte na
+ Viac aplikácií
+ Licencie tretích strán
+ Pozvať priateľov
+ Čauko, skús %1$s na %2$s
+ Pozvať cez
+ Ohodnotiť aplikáciu
+ Podporiť
+ Podporiť
+ Sledujte nás
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Dodatočné informácie
+ Verzia aplikácie: %s
+ Operačný systém zariadenia: %s
+
+
+ dúfam, že sa vám moja apka páči. Neobsahuje žiadne reklamy, podporte prosím jej vývoj zakúpením aplikácie Jednoduché ďakujem . Zaručí to aj to, že tento dialóg už neuvidíte.
+ Ďakujem!
+ ]]>
+
+ Zakúpiť
+ Prosím aktualizujte Jednoduché Ďakujem na najnovšiu verziu
+ Predtým, ako sa niečo opýtate, si prosím prečítajte Často Kladené Otázky, možno v nich nájdete svoju odpoveď.
+ Prečítať
+
+
+
+
+ iba vám oznamujem, že bola nedávno vydaná nová aplikácia:
+ %2$s
+ Viete ju stiahnuť kliknutím na názov.
+ Vďaka
+ ]]>
+
+
+
+ Často kladené otázky
+ Predtým, ako sa niečo opýtate si prosíme preštudujte
+ Prečo nevidím widget tejto apky na zozname widgetov?
+ Je to pravdepodobne spôsobené tým, že ste apku presunuli na SD kartu. Ide o systémové obmedzenie, ktoré v tomto prípade skryje widget apky.
+ Jediným riešením je presunutie apky späť na internú pamäť cez nastavnenia zariadenia.
+ Chcem vás podporiť, ale neviem poslať peniaze. Viem pre vás urobiť aj niečo iné?
+ Áno, samozrejme. Môžete napr. šíriť apky medzi známymi, alebo nám poslať užitočnú odozvu a hodnotenia. Viete pomôcť aj preložením apiek do nových jazykov, alebo upraviť existujúce preklady.
+ Návod nájdete na https://github.com/SimpleMobileTools/General-Discussion , alebo mi v prípade potreby pošlite email na hello@simplemobiletools.com.
+ Náhodou som vymazal niektoré súbory, viem ich obnoviť?
+ Žiaľ, nie. Súbory sú mazané okamžite po potvrdení, nie je dostupný žiadny smetný kôš.
+ Nepáčia sa mi farby widgetu, viem ich zmeniť?
+ Áno, po potiahnutí widgetu na plochu sa zobrazí konfiguračná obrazovka. Kliknutím na farebné štvorčeky v ľavom dolnom rohu viete zvoliť nové. Potiahnutím posuvníka viete upraviť aj priehľadnosť.
+ Viem nejakým spôsobom obnoviť vymazané súbory?
+ Ak dané súbory boli skutočne vymazané, tak nie. V nastaveniach aplikácie ale viete nastaviť používanie odpadkového koša miesto vymazávania súborov.
+
+
+ Táto aplikácia používa na uľahčenie práce nasledovné knižnice tretích strán. Ďakujeme.
+ Knižnice tretích strán
+ Kotlin (programovací jazyk)
+ Subsampling Scale Image View (priblížiteľné obrázky)
+ Glide (načítavanie a cachovanie obrázkov)
+ Picasso (načítavanie a cachovanie obrázkov)
+ Android Image Cropper (orezávanie a otáčanie obrázkov)
+ RecyclerView MultiSelect (označovanie viacerých položiek zo zoznamu)
+ RtlViewPager (swipovanie sprava vľavo)
+ Joda-Time (náhrada dátumov v Jave)
+ Stetho (odladenie databáz)
+ Otto (event bus)
+ PhotoView (priblížiteľné GIFká)
+ PatternLockView (ochrana vzorom)
+ Reprint (ochrana odtlačkom prsta)
+ Gif Drawable (zobrazovanie GIF)
+ AutoFitTextView (text meniaci veľkosť)
+ Robolectric (testovací framework)
+ Espresso (testovací pomocník)
+ Gson (parsovač JSON)
+ Leak Canary (detektor únikov pamäte)
+ Number Picker (upraviteľný vyberač čísel)
+ ExoPlayer (prehrávač videí)
+ VR Panorama View (zobrazovanie panorám)
+ Apache Sanselan (čítanie metadát obrázkov)
+ Android Photo Filters (filtre obrázkov)
+
diff --git a/commons/src/main/res/values-sv/strings.xml b/commons/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..95d100b
--- /dev/null
+++ b/commons/src/main/res/values-sv/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Avbryt
+ Spara som
+ Filen har sparats
+ Ogiltigt filformat
+ Minnet är fullt
+ Ett fel har uppstått: %s
+ Öppna med
+ Ingen giltig app hittades
+ Använd som
+ Värdet har kopierats till urklipp
+ Okänd
+ Alltid
+ Aldrig
+ Information
+ Anteckningar
+ Tar bort mappen \'%s\'
+ None
+ Label
+
+
+ Favoriter
+ Lägg till favoriter
+ Lägg till i favoriter
+ Ta bort från favoriter
+
+
+ Sök
+ Skriv in minst två tecken för att starta sökningen.
+
+
+ Filtrera
+ Inga objekt hittades.
+ Ändra filter
+
+
+ Lagringsbehörighet krävs
+ Kontaktbehörighet krävs
+ Kamerabehörighet krävs
+ Mikrofonbehörighet krävs
+
+
+ Byt namn på fil
+ Byt namn på mapp
+ Det gick inte att byta namn på filen
+ Det gick inte att byta namn på mappen
+ Du måste ange ett mappnamn
+ Det finns redan en mapp med samma namn
+ Det går inte att byta namn på rotmappen på ett lagringsutrymme
+ Mappen har fått ett nytt namn
+ Byter namn på mappen
+ Du måste ange ett filnamn
+ Filnamnet innehåller ogiltiga tecken
+ Filnamnet \'%s\' innehåller ogiltiga tecken
+ Filnamnstillägget får inte vara tomt
+ Källfilen %s finns inte
+
+
+ Kopiera
+ Flytta
+ Kopiera / Flytta
+ Kopiera till
+ Flytta till
+ Källa
+ Mål
+ Välj mål
+ Tryck här för att välja mål
+ Det gick inte att skriva till målet
+ Välj mål
+ Källa och mål kan inte vara samma
+ Det gick inte att kopiera filerna
+ Kopierar…
+ Filerna har kopierats
+ Ett fel har uppstått
+ Flyttar…
+ Filerna har flyttats
+ Det gick inte att flytta vissa filer
+ Det gick inte att kopiera vissa filer
+ Inga filer har valts
+ Sparar…
+ Det gick inte att skapa mappen %s
+ Det gick inte att skapa filen %s
+
+
+ Skapa ny
+ Mapp
+ Fil
+ Skapa ny mapp
+ Det finns redan en fil eller mapp med samma namn
+ Namnet innehåller ogiltiga tecken
+ Ange ett namn
+ Ett okänt fel har uppstått
+
+
+ Filen \"%s\" finns redan
+ Filen \"%s\" finns redan. Skriv över?
+ Mappen \"%s\" finns redan
+ Slå ihop
+ Skriv över
+ Hoppa över
+ Lägg till \'_1\'
+ Använd på alla konflikter
+
+
+ Välj mapp
+ Välj fil
+ Bekräfta åtkomst till extern lagring
+ Välj rotmappen på ditt SD-kort på nästa skärm, för att bevilja skrivrättigheter
+ Om du inte ser SD-kortet, prova detta
+ Bekräfta val
+
+
+ - %d objekt
+ - %d objekt
+
+
+
+
+ - %d objekt
+ - %d objekt
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Välj lagringsplats
+ Internt
+ SD-kort
+ Rot
+ Du har valt fel mapp, välj rotmappen på ditt SD-kort
+ SD-kortets och OTG-enhetens sökvägar kan inte vara samma
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Egenskaper
+ Sökväg
+ Valda objekt
+ Antal direkta döttrar
+ Antal filer
+ Upplösning
+ Varaktighet
+ Artist
+ Album
+ Brännvidd
+ Exponeringstid
+ ISO-grader
+ F-nummer
+ Kamera
+ EXIF
+ Låttitel
+
+
+ Bakgrundsfärg
+ Textfärg
+ Primär färg
+ Förgrundsfärg
+ Appikonens färg
+ Återställ förvalda värden
+ Ändra färg
+ Tema
+ Om du ändrar en färg, ändras temat till anpassat tema
+ Spara
+ Avbryt
+ Ångra ändringar
+ Är du säker på att du vill ångra dina ändringar?
+ Du har ändringar som inte är sparade. Spara innan du lämnar?
+ Använd färgerna i alla Simple-appar
+ Färgerna har uppdaterats. Ett nytt Tema som heter \'Delat\' har lagts till, använd det för att uppdatera alla apparnas färger i framtiden.
+
+ Simple Thank You för att låsa upp denna funktion och stödja utvecklingen. Tack!
+ ]]>
+
+
+
+ Ljust
+ Mörkt
+ Solariserat
+ Mörkrött
+ Svartvitt
+ Anpassat
+ Delat
+
+
+ Vad är nytt
+ * bara de större uppdateringarna visas här, det görs också alltid några mindre förbättringar
+
+
+ Ta bort
+ Ta bort
+ Byt namn
+ Dela
+ Dela via
+ Välj alla
+ Dölj
+ Visa
+ Dölj mapp
+ Visa mapp
+ Visa dolda objekt tillfälligt
+ Sluta visa dolda objekt
+ Du kan inte dela så här mycket innehåll på en gång
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sortera efter
+ Namn
+ Storlek
+ Senast ändrad
+ Fotodatum
+ Titel
+ Filnamn
+ Filnamnstillägg
+ Stigande
+ Fallande
+ Använd bara för denna mapp
+
+
+ Är du säker på att du vill fortsätta med borttagningen?
+ Är du säker på att du vill ta bort %s?
+ Är du säker på att du vill flytta %s till Papperskorgen?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Fråga inte igen i denna session
+ Ja
+ Nej
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN-kod
+ Ange PIN-kod
+ Ange en PIN-kod
+ Du har angett fel PIN-kod
+ Upprepa PIN-kod
+ Mönster
+ Rita mönster
+ Du har ritat fel mönster
+ Upprepa mönster
+ Fingeravtryck
+ Lägg till fingeravtryck
+ Placera fingret på fingeravtrycksläsaren
+ Autentiseringen misslyckades
+ Autentisering blockeras, försök igen om en stund
+ Du har inga registrerade fingeravtryck, lägg till några i Inställningarna på din enhet
+ Gå till Inställningar
+ Lösenordet har ställts in. Installera om appen om du glömmer bort det.
+ Skyddet har ställts in. Installera om appen om du inte kan ta bort skyddet.
+
+
+ Yesterday
+ Today
+ Imorgon
+ sekunder
+ minuter
+ timmar
+ dagar
+
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %d minut
+ - %d minuter
+
+
+ - %d timme
+ - %d timmar
+
+
+ - %d dag
+ - %d dagar
+
+
+ - %d vecka
+ - %d veckor
+
+
+ - %d månad
+ - %d månader
+
+
+ - %d år
+ - %d år
+
+
+
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %d minut
+ - %d minuter
+
+
+ - %d timme
+ - %d timmar
+
+
+
+
+ - %d sekund före
+ - %d sekunder före
+
+
+ - %d minut före
+ - %d minuter före
+
+
+ - %d timme före
+ - %d timmar före
+
+
+ - %d dag före
+ - %d dagar före
+
+
+
+
+ - %d sekund
+ - %d sekunder
+
+
+ - %d minut
+ - %d minuter
+
+
+ - %d timme
+ - %d timmar
+
+
+
+ Återstående tid tills alarmet ringer:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Kontrollera att alarmet fungerar som det ska innan du förlitar dig på det. Det kanske inte fungerar som det ska på grund av batterisparrelaterade systembegränsningar.
+ Kontrollera att påminnelserna fungerar som de ska innan du förlitar dig på dem. De kanske inte fungerar som de ska på grund av batterisparrelaterade systembegränsningar.
+
+
+ Alarm
+ Snooza
+ Stäng av
+ Ingen påminnelse
+ Vid start
+ Systemljud
+ Dina ljud
+ Lägg till ett nytt ljud
+ Inget ljud
+
+
+ Inställningar
+ Purchase Simple Thank You
+ Anpassa färger
+ Anpassa widgetens färger
+ Använd engelska
+ Visa inte vad som är nytt vid start
+ Visa dolda objekt
+ Teckenstorlek
+ Liten
+ Medelstor
+ Stor
+ Extra stor
+ Lösenordsskydda synligheten för dolda objekt
+ Lösenordsskydda hela appen
+ Behåll senast ändrad-tider när filer kopieras, flyttas eller får nya namn
+ Visa en informationsbubbla när jag rullar genom objekt genom att dra rullningslisten
+ Hindra aktivering av viloläge när appen körs i förgrunden
+ Hoppa alltid över dialogrutan för bekräftelse av borttagning
+ Aktivera uppdatering vid svepning från överkant
+ Använd 24-timmarsformat
+ Börja veckan på söndag
+ Widgetar
+ Använd alltid samma snooze-intervall
+ Snooze-intervall
+ Vibrera när jag trycker på knapparna
+ Flytta objekt till Papperskorgen istället för att ta bort dem
+ Rensningsintervall för Papperskorgen
+ Töm Papperskorgen
+ Lås skärmen i stående läge
+
+
+ Synlighet
+ Säkerhet
+ Rullning
+ Filåtgärder
+ Papperskorgen
+ Sparande
+ Start
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importerar…
+ Exporterar…
+ Importen lyckades
+ Exporten lyckades
+ Importen misslyckades
+ Exporten misslyckades
+ Importen av vissa poster misslyckades
+ Exporten av vissa poster misslyckades
+ Inga poster hittades för export
+
+
+ OTG
+ Välj rotmappen på OTG-enheten på nästa skärm, för att ge åtkomst
+ Du har valt fel mapp, välj rotmappen på din OTG-enhet
+
+
+ Januari
+ Februari
+ Mars
+ April
+ Maj
+ Juni
+ Juli
+ Augusti
+ September
+ Oktober
+ November
+ December
+
+
+ i januari
+ i februari
+ i mars
+ i april
+ i maj
+ i juni
+ i juli
+ i augusti
+ i september
+ i oktober
+ i november
+ i december
+
+ Måndag
+ Tisdag
+ Onsdag
+ Torsdag
+ Fredag
+ Lördag
+ Söndag
+
+ M
+ T
+ O
+ T
+ F
+ L
+ S
+
+ Mån
+ Tis
+ Ons
+ Tor
+ Fre
+ Lör
+ Sön
+
+
+ Om
+ Du hittar källkoden på
+ Skicka dina synpunkter och förslag till
+ Fler appar
+ Tredjepartslicenser
+ Bjud in vänner
+ Hej, läs om %1$s på %2$s
+ Bjud in via
+ Betygsätt oss
+ Donera
+ Donera
+ Följ oss
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Ytterligare info
+ Appversion: %s
+ Enhetens operativsystem: %s
+
+
+ jag hoppas att du gillar appen. Den innehåller ingen reklam. Stöd dess utveckling genom att köpa appen Simple Thank You , som även hindrar den här dialogrutan från att visas igen.
+ Tack!
+ ]]>
+
+ Köp
+ Uppdatera Simple Thank You till den senaste versionen
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ jag vill bara meddela att en ny app nyligen har publicerats:
+ %2$s
+ Du kan hämta den genom att trycka på titeln.
+ Tack
+ ]]>
+
+
+
+ Frågor och svar
+ Innan du ställer en fråga, läs först
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ Denna app använder följande tredjepartsbibliotek för att göra mitt liv enklare. Tack.
+ Tredjepartslicenser
+ Kotlin (programmeringsspråk)
+ Subsampling Scale Image View (zoombara bildvyer)
+ Glide (inläsning och cachning av bilder)
+ Picasso (inläsning och cachning av bilder)
+ Android Image Cropper (beskärning och rotering av bilder)
+ RecyclerView MultiSelect (markering av flera listobjekt)
+ RtlViewPager (höger-till-vänster-svepning)
+ Joda-Time (ersättning för Javas datumklass)
+ Stetho (avlusning av databaser)
+ Otto (händelsebuss)
+ PhotoView (zoombara GIF-bilder)
+ PatternLockView (mönsterlås)
+ Reprint (fingeravtryckslås)
+ Gif Drawable (inläsning av GIF-bilder)
+ AutoFitTextView (storleksändring av text)
+ Robolectric (testramverk)
+ Espresso (testhjälpklass)
+ Gson (JSON-parser)
+ Leak Canary (minnesläckagedetektor)
+ Number Picker (anpassningsbar talväljare)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-sw600dp/dimens.xml b/commons/src/main/res/values-sw600dp/dimens.xml
new file mode 100644
index 0000000..9c2dc96
--- /dev/null
+++ b/commons/src/main/res/values-sw600dp/dimens.xml
@@ -0,0 +1,13 @@
+
+ 4dp
+ 12dp
+ 50dp
+ 80dp
+ 40dp
+
+ 38dp
+
+ 18sp
+ 22sp
+ 26sp
+
diff --git a/commons/src/main/res/values-tr/strings.xml b/commons/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..a55b3b8
--- /dev/null
+++ b/commons/src/main/res/values-tr/strings.xml
@@ -0,0 +1,560 @@
+
+ Tamam
+ İptal
+ Farklı kaydet
+ Dosya başarıyla kaydedildi
+ Geçersiz dosya biçimi
+ Yetersiz hafıza hatası
+ Bir hata oluştu: %s
+ Birlikte aç
+ Geçerli bir uygulama bulunamadı
+ Ayarla
+ Değer panoya kopyalandı
+ Bilinmeyen
+ Her zaman
+ Asla
+ Ayrıntılar
+ Notlar
+ \'%s\' klasörü siliniyor
+ None
+ Label
+
+
+ Favoriler
+ Favori ekle
+ Favorilere ekle
+ Favorilerden kaldır
+
+
+ Ara
+ Aramaya başlamak için en az 2 karakter yazın.
+
+
+ Filtre
+ Hiçbir öğe bulunamadı.
+ Filtreyi değiştir
+
+
+ Depolama izni gerekiyor
+ Kişiler izni gerekiyor
+ Kamera izni gerekiyor
+ Ses izni gerekiyor
+
+
+ Dosyayı yeniden adlandır
+ Klasörü yeniden adlandır
+ Dosya yeniden adlandırılamıyor
+ Klasör yeniden adlandırılamıyor
+ Klasör adı boş olamaz
+ Bu ada sahip bir klasör zaten var
+ Depolama biriminin kök klasörü yeniden adlandıramaz
+ Klasör başarıyla yeniden adlandırıldı
+ Klasörü yeniden adlandırma
+ Dosya adı boş olamaz
+ Dosya adı geçersiz karakterler içeriyor
+ Dosya adı \'%s\' geçersiz karakterler içeriyor
+ Uzantı boş olamaz
+ %s kaynak dosyası mevcut değil
+
+
+ Kopyala
+ Taşı
+ Kopyala / Taşı
+ Kopyala:
+ Taşı:
+ Kaynak
+ Hedef
+ Hedef seç
+ Hedef seçmek için buraya tıklayın
+ Seçilen hedefe yazılamadı
+ Lütfen bir hedef seçin
+ Kaynak ve hedef aynı olamaz
+ Dosyalar kopyalanamadı
+ Kopyalanıyor…
+ Dosyalar başarıyla kopyalandı
+ Bir hata oluştu
+ Taşınıyor…
+ Dosyalar başarıyla taşındı
+ Bazı dosyalar taşınamadı
+ Bazı dosyalar kopyalanamadı
+ Hiçbir dosya seçilmedi
+ Kaydediliyor…
+ %s klasörü oluşturulamıyor
+ %s dosyası oluşturulamıyor
+
+
+ Yeni oluştur
+ Klasör
+ Dosya
+ Yeni klasör oluştur
+ Bu ada sahip bir dosya veya klasör zaten var
+ İsim, geçersiz karakterler içeriyor
+ Lütfen yeni bir ad girin
+ Bilinmeyen bir hata oluştu
+
+
+ \"%s\" dosyası zaten var
+ \"%s\" dosyası zaten var. Üzerine yazılsın mı?
+ \"%s\" klasörü zaten var
+ Birleştir
+ Üzerine yaz
+ Atla
+ \'_1\' ile ekle
+ Tümüne uygula
+
+
+ Bir klasör seç
+ Bir dosya seç
+ Harici depolama erişimini onayla
+ Yazma erişimi vermek için lütfen bir sonraki ekranda SD kartın kök klasörünü seçin
+ SD kartı görmüyorsanız, bunu deneyin
+ Seçimi onayla
+
+
+ - %d öğe
+ - %d öğe
+
+
+
+
+ - %d öğe
+ - %d öğe
+
+
+
+ - %d öğe siliniyor
+ - %d öğe siliniyor
+
+
+
+ Depolama seç
+ Dahili
+ SD Kart
+ Root
+ Yanlış klasör seçildi, lütfen SD kartınızın kök klasörünü seçin
+ SD kart ve OTG cihaz yolları aynı olamaz
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Özellikler
+ Yol
+ Seçilen öğeler
+ Alt öğe sayısı
+ Toplam dosya sayısı
+ Çözünürlük
+ Süre
+ Sanatçı
+ Albüm
+ Odak uzaklığı
+ Pozlama süresi
+ ISO hızı
+ Diyafram
+ Kamera
+ EXIF
+ Şarkı başlığı
+
+
+ Arka plan rengi
+ Metin rengi
+ Ana renk
+ Ön plan rengi
+ Uygulama simgesi rengi
+ Varsayılanları geri yükle
+ Rengi değiştir
+ Tema
+ Bir rengin değiştirilmesi, Özel temaya geçilmesini sağlayacaktır
+ Kaydet
+ Çık
+ Değişiklikleri geri al
+ Değişikliklerinizi geri almak istediğinizden emin misiniz?
+ Kaydedilmemiş değişiklikleriniz var. Çıkmadan önce kaydedilsin mi?
+ Tüm Basit Uygulamalara renk uygula
+ "Renkler başarıyla güncellendi. 'Paylaşılan' adlı yeni bir Tema eklendi, lütfen gelecekte tüm uygulama renklerini güncellemek için kullanınız."
+
+ Simple Thank You\'yu satın alın. Teşekkürler!
+ ]]>
+
+
+
+ Aydınlık
+ Karanlık
+ Solarized
+ Koyu Kırmızı
+ Siyah & Beyaz
+ Özel
+ Paylaşılan
+
+
+ Yenilikler
+ * yalnızca büyük güncellemeler burada listelenmiştir; daima bazı küçük geliştirmeler de vardır
+
+
+ Sil
+ Kaldır
+ Yeniden adlandır
+ Paylaş
+ Şununla paylaş
+ Tümünü seç
+ Gizle
+ Göster
+ Klasörü gizle
+ Klasörü göster
+ Geçici olarak gizliyi göster
+ Gizli medyayı göstermeyi bırak
+ Bu kadar çok içeriği bir kerede paylaşamazsınız
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sıralama ölçütü
+ Ad
+ Boyut
+ Son değiştirilme
+ Alınan tarih
+ Başlık
+ Dosya adı
+ Uzantı
+ Artan
+ Azalan
+ Sadece bu klasör için kullan
+
+
+ Silme işlemine devam etmek istediğinizden emin misiniz?
+ %s gerçekten silinsin mi?
+ %s gerçekten Geri Dönüşüm Kutusuna taşınsın mı?
+ Bu öğeyi silmek istediğinizden emin misiniz?
+ Bu öğeyi Geri Dönüşüm Kutusu\'na taşımak istediğinizden emin misiniz?
+ Bu oturumda tekrar sorma
+ Evet
+ Hayır
+
+
+ - UYARI: Bir klasör siliyorsunuz
+ - UYARI: %d klasör siliyorsunuz
+
+
+
+ PIN
+ PIN girin
+ Lütfen PIN girin
+ Yanlış PIN
+ PIN\'i tekrarla
+ Desen
+ Desen ekle
+ Yanlış desen
+ Deseni tekrarla
+ Parmak izi
+ Parmak izi ekle
+ Lütfen parmağınızı parmak izi sensörüne yerleştirin
+ Kimlik doğrulama başarısız
+ Kimlik doğrulama engellendi, lütfen birazdan tekrar deneyin
+ Parmak iziniz kayıtlı değil, lütfen cihazınızın Ayarlar bölümünden ekleyin
+ Ayarlar\'a git
+ Parola kurulumu başarılı oldu. Lütfen unuttuğunuz durumda uygulamayı yeniden yükleyin.
+ Koruma kurulumu başarıyla gerçekleştirildi. Sıfırlama ile ilgili sorunlar olması durumunda lütfen uygulamayı yeniden yükleyin.
+
+
+ Dün
+ Bugün
+ Yarın
+ saniye
+ dakika
+ saat
+ gün
+
+
+ - %d saniye
+ - %d saniye
+
+
+ - %d dakika
+ - %d dakika
+
+
+ - %d saat
+ - %d saat
+
+
+ - %d gün
+ - %d gün
+
+
+ - %d hafta
+ - %d hafta
+
+
+ - %d ay
+ - %d ay
+
+
+ - %d yıl
+ - %d yıl
+
+
+
+
+ - %d saniye
+ - %d saniye
+
+
+ - %d dakika
+ - %d dakika
+
+
+ - %d saat
+ - %d saat
+
+
+
+
+ - %d saniye önce
+ - %d saniye önce
+
+
+ - %d dakika önce
+ - %d dakika önce
+
+
+ - %d saat önce
+ - %d saat önce
+
+
+ - %d gün önce
+ - %d gün önce
+
+
+
+
+ - %d saniye
+ - %d saniye
+
+
+ - %d dakika
+ - %d dakika
+
+
+ - %d saat
+ - %d saat
+
+
+
+ Alarmın çalmasına şu kadar kaldı:\n%s
+ Hatırlatıcı tetiklenene kadar kalan süre:\n%s
+ Lütfen alarma güvenmeden önce düzgün çalıştığından emin olun. Pil tasarrufu ile ilgili sistem kısıtlamaları nedeniyle yanlış davranabilir.
+ Lütfen hatırlatıcılara güvenmeden önce düzgün çalıştıklarından emin olun. Pil tasarrufu ile ilgili sistem kısıtlamaları nedeniyle yanlış davranabilirler.
+
+
+ Alarm
+ Ertele
+ Kapat
+ Hatırlatma yok
+ Başlangıçta
+ Sistem sesleri
+ Sesleriniz
+ Yeni bir ses ekle
+ Ses yok
+
+
+ Ayarlar
+ Purchase Simple Thank You
+ Renkleri özelleştir
+ Widget renklerini özelleştir
+ İngilizce dilini kullan
+ Başlangıçta Yenilikleri göstermeyi önle
+ Gizli öğeleri göster
+ Yazı tipi boyutu
+ Küçük
+ Orta
+ Büyük
+ Çok büyük
+ Parola korumalı gizli öğe görünürlüğü
+ Parola tüm uygulamayı korur
+ Dosya kopyalama/taşıma/yeniden adlandırmada eski son değiştirilme değerini koru
+ Kaydırma çubuğunu sürükleyerek öğeleri kaydırmada bilgi balonunu göster
+ Uygulama ön plandayken telefonun uykuya geçmesini önle
+ Her zaman silme onayı penceresini atla
+ Üstten yenilemek için çekmeyi etkinleştir
+ 24 saat biçimini kullan
+ Pazar günü hafta başlangıcı
+ Widget\'lar
+ Her zaman aynı erteleme süresini kullan
+ Erteleme süresi
+ Düğmeye basıldığında titret
+ Öğeleri silmek yerine Geri Dönüşüm Kutusuna taşı
+ Geri Dönüşüm Kutusu temizleme süresi
+ Geri Dönüşüm Kutusunu boşalt
+ Dikey modu zorla
+
+
+ Görünürlük
+ Güvenlik
+ Kaydırma
+ Dosya işlemleri
+ Geri Dönüşüm Kutusu
+ Kaydetme
+ Başlangıç
+ Metin
+
+
+ Bu dosyayı geri yükle
+ Seçilen dosyaları geri yükle
+ Tüm dosyaları geri yükle
+ Geri Dönüşüm Kutusu başarıyla boşaltıldı
+ Dosyalar başarıyla geri yüklendi
+ Geri Dönüşüm Kutusu\'nu boşaltmak istediğinizden emin misiniz? Dosyalar kalıcı olarak kaybolacak.
+ Geri Dönüşüm Kutusu boş
+
+
+ İçe aktarılıyor…
+ Dışa aktarılıyor…
+ İçe aktarma başarılı
+ Dışa aktarma başarılı
+ İçe aktarılamadı
+ Dışa aktarılamadı
+ Bazı öğeler içe aktarılamadı
+ Bazı öğeler dışa aktarılamadı
+ Dışa aktarılacak öğe bulunamadı
+
+
+ OTG
+ Erişim vermek için lütfen bir sonraki ekranda OTG cihazının kök klasörünü seçin
+ Yanlış klasör seçildi, lütfen OTG cihazınızın kök klasörünü seçin
+
+
+ Ocak
+ Şubat
+ Mart
+ Nisan
+ Mayıs
+ Haziran
+ Temmuz
+ Ağustos
+ Eylül
+ Ekim
+ Kasım
+ Aralık
+
+
+ Ocak ayında
+ Şubat ayında
+ Mart ayında
+ Nisan ayında
+ Mayıs ayında
+ Haziran ayında
+ Temmuz ayında
+ Ağustos ayında
+ Eylül ayında
+ Ekim ayında
+ Kasım ayında
+ Aralık ayında
+
+ Pazartesi
+ Salı
+ Çarşamba
+ Perşembe
+ Cuma
+ Cumartesi
+ Pazar
+
+ P
+ S
+ Ç
+ P
+ C
+ C
+ P
+
+ Pzt
+ Sal
+ Çar
+ Per
+ Cum
+ Cmt
+ Paz
+
+
+ Hakkında
+ Kaynak kodları için ziyaret edin
+ Görüşlerinizi veya önerilerinizi buraya gönderin
+ Daha fazla uygulama
+ Üçüncü taraf lisansları
+ Arkadaşlarınızı davet edin
+ Selam, %1$s adlı harika bir uygulama buldum. %2$s adresinden indir
+ Şununla davet et
+ Bizi değerlendirin
+ Bağış yapın
+ Bağış yapın
+ Bizi takip edin
+ v %1$s\nTelif hakkı © Simple Mobile Tools %2$d
+ Ek bilgiler
+ Uygulama sürümü: %s
+ Cihaz İS: %s
+
+
+ umarım uygulamadan memnunsunuzdur. Hiçbir reklam içermiyor, bu yüzden lütfen Simple Thank You uygulamasını satın alarak geliştirmeyi destekleyip, bu pencerenin tekrar görünmesini de önleyin.
+ Teşekkürler!
+ ]]>
+
+ Satın al
+ Lütfen Simple Thank You\'yu son sürüme güncelleyin
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ kısa bir süre önce yeni uygulamamın yayınlandığını bilmenizi istiyorum:
+ %2$s
+ Başlığa basarak indirebilirsiniz.
+ Teşekkürler
+ ]]>
+
+
+
+ Sık sorulan sorular
+ Bir soru sormadan önce, lütfen şurayı okuyun
+ Uygulamanın widget\'ını widget\'lar listesinde neden göremiyorum?
+ Büyük olasılıkla uygulamayı SD karta taşıdığınız içindir. Bu durumda verilen uygulama widget\'larını gizleyen bir Android sistem sınırlaması var. Tek çözüm, cihaz ayarlarınızı kullanarak uygulamayı Dahili Depolama birimine geri taşımaktır.
+ Seni desteklemek istiyorum ama para bağışlayamıyorum. Yapabileceğim başka bir şey var mı?
+ Evet, tabi ki. Uygulamalar hakkında tanıtım yapabilir veya iyi geribildirim ve derecelendirmeler verebilirsiniz.
+ Kılavuzu https://github.com/SimpleMobileTools/General-Discussion adresinde bulabilir, veya yardıma ihtiyacınız varsa bana hello@simplemobiletools.com adresinden bir mesaj gönderebilirsiniz.
+ Bazı dosyaları yanlışlıkla sildim, onları nasıl kurtarabilirim?
+ Ne yazık ki, yapamazsınız. Onay penceresinden sonra dosyalar anında silinir, kullanılabilir çöp kutusu yoktur.
+ Widget renklerini beğenmedim, değiştirebilir miyim?
+ Evet, ana ekranınızda bir widget\'ı sürüklediğinizde, bir widget yapılandırma ekranı görünür. Sol alt köşede renkli kareler göreceksiniz, sadece yeni bir renk seçmek için onlara basın. Alfa\'yı da ayarlamak için kaydırıcıyı kullanabilirsiniz.
+ Bir şekilde silinen dosyaları geri yükleyebilir miyim?
+ Gerçekten silinmişlerse, yapamazsınız. Bununla birlikte, gelecekte dosyaları kaybetmemek için uygulama ayarlarındaki Silmek yerine Geri Dönüşüm Kutusuna taşı seçeneğini etkinleştirebilirsiniz. Bu, dosyaları silmek yerine sadece taşır.
+
+
+ Bu uygulama hayatımı kolaylaştırmak için aşağıdaki üçüncü taraf kitaplıklarını kullanır. Teşekkürler.
+ Üçüncü taraf lisansları
+ Kotlin (programlama dili)
+ Subsampling Scale Image View (yakınlaştırılabilir görüntü görünümleri)
+ Glide (görüntü yükleme ve önbellekleme)
+ Picasso (görüntü yükleme ve önbellekleme)
+ Android Image Cropper (görüntü kırpma ve döndürme)
+ RecyclerView MultiSelect (birden çok liste öğesi seçme)
+ RtlViewPager (sağdan sola kaydırma)
+ Joda-Time (Java tarih değiştirme)
+ Stetho (hata ayıklama veritabanları)
+ Otto (event bus)
+ PhotoView (yakınlaştırılabilir GIF\'ler)
+ PatternLockView (desen koruması)
+ Reprint (parmak izi koruması)
+ Gif Drawable (GIF\'leri yükleme)
+ AutoFitTextView (metin boyutunu değiştirme)
+ Robolectric (test çerçevesi)
+ Espresso (test yardımcısı)
+ Gson (JSON ayrıştırıcısı)
+ Leak Canary (bellek sızıntısı dedektörü)
+ Number Picker (özelleştirilebilir sayı seçici)
+ ExoPlayer (video oynatıcı)
+ VR Panorama View (panorama görüntüleme)
+ Apache Sanselan (görüntü meta verileri okuma)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values-v21/styles.xml b/commons/src/main/res/values-v21/styles.xml
new file mode 100644
index 0000000..071cd28
--- /dev/null
+++ b/commons/src/main/res/values-v21/styles.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/commons/src/main/res/values-zh-rCN/strings.xml b/commons/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..36ca729
--- /dev/null
+++ b/commons/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,559 @@
+
+ 确认
+ 取消
+ 保存
+ 文件保存成功
+ 无效文件格式
+ 内存不足
+ 啊哦,出错啦: %s
+ 打开方式
+ 未找到可用应用
+ 设置为
+ 颜色值已复制到剪贴板
+ 未知
+ 总是
+ 从不
+ 详细
+ 笔记
+ 正在删除目录 \'%s\'
+ None
+ Label
+
+
+ 收藏
+ 添加收藏
+ 添加到收藏
+ 从收藏中移除
+
+
+ 搜索
+ 请输入至少两个字符来开始搜索
+
+
+ 过滤
+ 找不到任何项目。
+ 更改过滤器
+
+
+ 请授予存储权限
+ 请授予通讯录权限
+ 请授予相机权限
+ 请授予音频权限
+
+
+ 重命名文件
+ 重命名文件夹
+ 无法重命名文件
+ 无法重命名文件夹
+ 文件夹名不能为空
+ 文件夹名已存在
+ 无法重命名存储器的根目录
+ 文件夹重命名成功
+ 重命名中…
+ 文件名不能为空
+ 文件名包含非法字符
+ Filename \'%s\' contains invalid characters
+ 扩展名不能为空
+ 原文件 %s 不存在
+
+
+ 复制
+ 移动
+ 复制/移动
+ 复制到
+ 移动到
+ 原始路径
+ 目标路径
+ 选择目标路径
+ 点击此处以设置目标路径
+ 无法写入到选中目标路径
+ 请选择目标路径
+ 原始路径和目标路径不能相同
+ 无法复制文件
+ 正在复制…
+ 复制成功
+ 操作失败
+ 正在移动…
+ 文件移动成功
+ 无法移动相同文件
+ 无法复制相同文件
+ 未选择文件
+ 正在保存…
+ 创建文件夹 %s 失败
+ 创建文件 %s 失败
+
+
+ 新建
+ 文件夹
+ 文件
+ 新建文件夹
+ 同名文件夹或文件已存在
+ 名称包含非法字符
+ 请输入名称
+ 未知错误
+
+
+ 文件 \"%s\" 已存在
+ 文件 \"%s\" 已存在。是否覆盖?
+ 文件夹 \"%s\" 已存在
+ 合并
+ 覆盖
+ 跳过
+ 应用到 \'_1\'
+ 应用到全部冲突项
+
+
+ 选择文件夹
+ 选择文件
+ 确认外部存储器访问权限
+ 请选择 SD 卡根目录并授予写权限
+ 如果您未找到 SD 卡目录,请尝试
+ 确认选择
+
+
+ - %d 个项目
+ - %d 个项目
+
+
+
+
+ - %d 个项目
+ - %d 个项目
+
+
+
+ - 正在删除 %d 个项目
+ - 正在删除 %d 个项目
+
+
+
+ 选择存储器
+ 内部存储器
+ SD 卡
+ 根目录
+ 目录选择错误,请选择 SD 卡
+ SD 卡和 OTG 设备路径不能相同
+ 您似乎已将应用程序安装在 SD 卡上,这使得应用的小部件不可用。你将无法在小部件列表里看到它们。
+ 这是一个系统限制,所以如果你想要使用这些小部件,你必须将应用移回内部存储器。
+
+
+ 属性
+ 路径
+ 已选择项目
+ 子目录数
+ 总文件数
+ 分辨率
+ 时长
+ 艺术家
+ 专辑
+ 焦距
+ 曝光时间
+ ISO 速度
+ 光圈
+ 相机
+ EXIF
+ 歌曲标题
+
+
+ 背景色
+ 文本颜色
+ 主体色
+ 前景色
+ 应用图标颜色
+ 恢复默认
+ 修改颜色
+ 主题
+ 更改颜色将切换到自定义主题
+ 保存
+ 丢弃
+ 撤销更改
+ 是否撤销您的更改?
+ 您尚未保存更改,是否保存?
+ 应用到所有简约系列应用
+ 颜色更改成功。已添加名为“共享”的新主题,以后你可以使用它来更改所有应用的颜色。
+
+ Simple Thank You 以解锁此功能并支持开发。感谢!
+ ]]>
+
+
+
+ 浅色主题
+ 深色主题
+ Solarized
+ 深红
+ 黑白
+ 自定义
+ 共享
+
+
+ 更新日志
+ * 此处仅列举了重大更新,更多修正可在使用中体验
+
+
+ 删除
+ 移除
+ 重命名
+ 分享
+ 分享到
+ 全选
+ 隐藏
+ 取消隐藏
+ 隐藏文件夹
+ 取消隐藏文件夹
+ 显示/隐藏缓存内容
+ 不显示隐藏的媒体文件
+ 您不能一次分享太多的内容
+ 清空并禁用回收站
+ 撤销
+ 重做
+
+
+ 排序方式
+ 名称
+ 大小
+ 修改日期
+ 拍摄日期
+ 标题
+ 文件名
+ 扩展名
+ 递增
+ 递减
+ 仅应用于此文件夹
+
+
+ 是否执行此删除操作?
+ 你确定要删除 %s?
+ 你确定要移动 %s到回收站?
+ 你确定要删除此项目吗?
+ 你确定要移动此项目到回收站吗?
+ 不再提醒
+ 是
+ 否
+
+
+ - 警告: 你正在删除此目录
+ - 警告: 你正在删除 %d 目录
+
+
+
+ 密码
+ 输入密码
+ 请输入密码
+ 密码错误
+ 重复密码
+ 图案
+ 绘制图案
+ 图案错误
+ 重复图案
+ 指纹
+ 添加指纹
+ 请将手指放在指纹传感器上
+ 验证失败
+ 验证已被阻止,请稍后再试
+ 您还没有注册指纹,请先给你的设备添加一些指纹
+ 去设置
+ 密码设置成功。如果你不慎遗忘了,请重新安装本应用。
+ 指纹保护设置成功。如果出现问题,请重新安装本应用。
+
+
+ 昨天
+ 今天
+ 明天
+ 秒
+ 分钟
+ 小时
+ 天
+
+
+ - %d 秒
+ - %d 秒
+
+
+ - %d 分钟
+ - %d 分钟
+
+
+ - %d 小时
+ - %d 小时
+
+
+ - %d 日
+ - %d 日
+
+
+ - %d 周
+ - %d 周
+
+
+ - %d 月
+ - %d 月
+
+
+ - %d 年
+ - %d 年
+
+
+
+
+ - %d 秒
+ - %d 秒
+
+
+ - %d 分钟
+ - %d 分钟
+
+
+ - %d 小时
+ - %d 小时
+
+
+
+
+ - %d 秒前
+ - %d 秒前
+
+
+ - %d 分钟前
+ - %d 分钟前
+
+
+ - %d 小时前
+ - %d 小时前
+
+
+ - %d 天前
+ - %d 天前
+
+
+
+
+ - %d 秒
+ - %d 秒
+
+
+ - %d 分钟
+ - %d 分钟
+
+
+ - %d 小时
+ - %d 小时
+
+
+
+ 距闹钟响起还有:\n%s
+ 距提醒触发还有:\n%s
+ 在依赖它之前,请确保闹钟能够正常工作。由于系统与节电有关的限制,它有时可能会失灵。
+ 在依赖它们之前,请确保提醒能够正常工作。由于系统与节电有关的限制,它们有时可能会失灵。
+
+
+ 闹钟
+ 贪睡
+ 关闭
+ 不再提醒
+ 开始时
+ 系统铃声
+ 你的铃声
+ 添加新的铃声
+ 无铃声
+
+
+ 设置
+ 购买 Simple Thank You
+ 自定义颜色
+ 自定义小部件颜色
+ 强制使用英语语言
+ 启动时不再显示更新内容
+ 显示隐藏的项目
+ 字体大小
+ 小
+ 中
+ 大
+ 巨大
+ 使用密码保护隐藏项
+ 使用密码保护本应用
+ 在复制/移动/重命名文件时保留旧的修改日期
+ 拖动滚动条时在滚动项目旁显示一个信息气泡
+ 阻止设备自动休眠
+ 忽略删除确认对话框
+ 启用顶部上拉刷新
+ 使用 24 小时制
+ 每周以周日开头
+ 小部件
+ 始终使用相同的贪睡时间
+ 贪睡时间
+ 按下按键后震动
+ 将项目转移到回收站而不是直接删除
+ 回收站清理间隔
+ 清空回收站
+ 强制人像模式
+
+
+ 可见度
+ 安全
+ 滚动
+ 文件操作
+ 回收站
+ 正在保存
+ 启动
+ 文本
+
+
+ 恢复此文件
+ 恢复选中的文件
+ 恢复所有文件
+ 清空回收站成功
+ 恢复文件成功
+ 您确定要清空回收站吗?这些文件将永久丢失。
+ 回收站是空的
+
+
+ 正在导入…
+ 正在导出…
+ 导入成功
+ 导出成功
+ 导入失败
+ 导出失败
+ 部分项目导入失败
+ 部分项目导出失败
+ 未找到要导出的项目
+
+
+ OTG
+ 请在下一界面选择 OTG 设备的根文件夹以授予访问权限
+ 选择错误,请正确选择 OTG 设备的根文件夹
+
+
+ 一月
+ 二月
+ 三月
+ 四月
+ 五月
+ 六月
+ 七月
+ 八月
+ 九月
+ 十月
+ 十一月
+ 十二月
+
+
+ 一月
+ 二月
+ 三月
+ 四月
+ 五月
+ 六月
+ 七月
+ 八月
+ 九月
+ 十月
+ 十一月
+ 十二月
+
+ 周一
+ 周二
+ 周三
+ 周四
+ 周五
+ 周六
+ 周日
+
+ 一
+ 二
+ 三
+ 四
+ 五
+ 六
+ 日
+
+ 周一
+ 周二
+ 周三
+ 周四
+ 周五
+ 周六
+ 周日
+
+
+ 关于
+ 应用源码
+ 发送反馈
+ 更多应用
+ 开放源代码
+ 分享给好友
+ 请前往 %2$s 看看我们的 %1$s 吧!
+ 分享到
+ 为我们打分
+ 捐赠
+ 捐赠
+ 关注我们
+ v %1$s Copyright © Simple Mobile Tools %2$d
+ 额外信息
+ 应用版本:%s
+ 设备系统:%s
+
+
+ 希望你喜欢这个应用。它不包含广告,所以请购买Simple Thank You 这个应用支持一下,这样此对话框将不再出现。
+ 非常感谢!
+ ]]>
+
+ 购买
+ 请更新 Simple Thank You 到最新版本
+ 提问前请先阅读常见问题列表,或许已经有回答。
+ 阅读
+
+
+
+
+ 最近发布了一个新的应用:
+ %2$s
+ 你可以点击标题来下载。
+ 谢谢。
+ ]]>
+
+
+
+ 常见问题
+ 在提问之前,请先阅读
+ 为什么我在应用小部件列表里没有找到这个应用的小部件?
+ 这很可能是因为你将应用程序移动到了SD卡。由于 Android 系统的限制,在这种情况下会隐藏应用程序的小部件。 唯一的解决方法是在系统设置中将应用程序移回内部存储。
+ 我想支持你,但我不能捐赠金钱。 还有什么是我能够做的吗?
+ 当然有。你可以帮我宣传这个应用,或给个五星好评。你还可以为我的应用翻译新的语言来帮助你,或者是更新一些现有的翻译。你可以在 https://github.com/SimpleMobileTools/General-Discussion 找到该指南,如果你需要帮助,也可以发邮件 hello@simplemobiletools.com 给我。
+ 我该怎么恢复误删的文件?
+ 不幸的是,你无法恢复。在确认对话框后文件将被立即删除,你并没有回收站用。
+ 我不喜欢小部件的颜色,我可以修改吗?
+ 是可以的,当你在主屏幕上拖动小部件时,会出现小部件配置界面。你会在左下角看到彩色的方块,只需按下它们即可选择新的颜色。你还可以通过滑块来调整 Alpha 值。
+ 我可以恢复已删除的文件吗?
+ 如果你真的把它们删除了,那么你无法恢复。不过,您可以启用回收站,这样只会移动文件而不是直接删除。
+
+
+ 此应用使用了以下三方库。谢谢。
+ 开放源代码
+ Kotlin (编程语言)
+ Subsampling Scale Image View (可缩放视图)
+ Glide (图像加载和缓存)
+ Picasso (图像加载和缓存)
+ Android Image Cropper (图像裁剪和旋转)
+ RecyclerView MultiSelect (多列表项选择)
+ RtlViewPager (从右向左滑动)
+ Joda-Time (Java日期替换)
+ Stetho (调试数据库)
+ Otto (事件总线)
+ PhotoView (可缩放 GIFs)
+ PatternLockView (图案保护)
+ Reprint (指纹保护)
+ Gif Drawable (加载 GIFs)
+ AutoFitTextView (调整文本大小)
+ Robolectric (测试框架)
+ Espresso (测试助手)
+ Gson (JSON 解析器)
+ Leak Canary (内存泄漏监测器)
+ Number Picker (可定制的号码选择器)
+ ExoPlayer (视频播放器)
+ VR Panorama View (显示全景图)
+ Apache Sanselan (读取图像元数据)
+ Android Photo Filters (图像过滤器)
+
diff --git a/commons/src/main/res/values-zh-rTW/strings.xml b/commons/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..86846e1
--- /dev/null
+++ b/commons/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,560 @@
+
+ 確定
+ 取消
+ 儲存
+ 成功儲存檔案
+ 無效的檔案格式
+ 記憶體不足
+ 發生錯誤: %s
+ 以其他應用程式開啟
+ 找不到應用程式
+ 設為
+ 數值複製到剪貼簿
+ 未知的
+ 總是
+ 從不
+ 詳細資訊
+ 筆記
+ 資料夾 \'%s\' 刪除中
+ 無
+ Label
+
+
+ 我的最愛
+ 添加我的最愛
+ 加入我的最愛
+ 移除我的最愛
+
+
+ 搜尋
+ 輸入兩個字以上來開始搜尋。
+
+
+ 篩選
+ 未發現項目
+ 更改篩選條件
+
+
+ 儲存空間權限是必要的
+ 聯絡人權限是必要的
+ 相機權限是必要的
+ 音訊權限是必要的
+
+
+ 重新命名檔案
+ 重新命名資料夾
+ 無法重新命名檔案
+ 無法重新命名資料夾
+ 資料夾名稱不得空白
+ 已經存在這名稱的資料夾
+ 無法重新命名根目錄
+ 成功重新命名資料夾
+ 資料夾重新命名中
+ 檔名不得空白
+ 檔名包含無效字元
+ 檔名 \'%s\' 包含無效字元
+ 副檔名不得空白
+ 原始檔案 %s 不存在
+
+
+ 複製
+ 移動
+ 複製 / 移動
+ 複製
+ 移動
+ 來源
+ 目標
+ 選擇目標
+ 點此選擇目標
+ 無法寫入選擇的目標
+ 請選擇目標
+ 目標跟來源不能相同
+ 無法複製檔案
+ 複製中…
+ 成功複製檔案
+ 發生錯誤
+ 移動中…
+ 成功移動檔案
+ 部分檔案無法被移動
+ 部分檔案無法被複製
+ 未選擇檔案
+ 儲存中…
+ 無法建立資料夾 %s
+ 無法建立檔案 %s
+
+
+ 新增
+ 資料夾
+ 檔案
+ 新增資料夾
+ 已經存在相同名稱的檔案或資料夾
+ 名稱包含無效的字元
+ 請輸入一個名稱
+ 發生未知的錯誤
+
+
+ 檔案 \"%s\" 已經存在
+ 檔案 \"%s\" 已經存在。 要覆蓋嗎?
+ 資料夾 \"%s\" 已經存在
+ 覆蓋
+ 跳過
+ 附加 \'_1\'
+ 添加到全部衝突
+
+
+ 選擇資料夾
+ 選擇檔案
+ 確認外部儲存空間存取權限
+ 請在下個畫面選擇SD卡的根目錄,以授予存取權限
+ "如果看不到SD卡,試試這個"
+ 確認選擇
+
+
+ - %d個項目
+ - %d個項目
+
+
+
+
+ - %d個項目
+ - %d個項目
+
+
+
+ - %d個項目刪除中
+ - %d個項目刪除中
+
+
+
+ 選擇儲存空間
+ 內部儲存空間
+ SD卡
+ 根目錄
+ 選擇了錯誤的資料夾,請選擇SD卡的根目錄
+ SD卡和OTG裝置的路徑不能相同
+ 你似乎將應用程式安裝在SD卡,那會造成應用程式小工具無法使用。你甚至不會在可用的小工具列表中見到它們。
+ 那是個系統限制,所以如果你想要使用小工具,你必須把應用程式移動回內部儲存空間。
+
+
+ 屬性
+ 路徑
+ 選擇的項目
+ 子目錄數
+ 總檔案數
+ 解析度
+ 時間長度
+ 演唱者
+ 專輯
+ 焦距
+ 曝光時間
+ ISO速度
+ 光圈
+ 相機
+ EXIF
+ 歌名
+
+
+ 背景顏色
+ 文字顏色
+ 主體顏色
+ 前景顏色
+ 應用程式圖標顏色
+ 恢復預設
+ 改變顏色
+ 主題
+ 改變顏色將切換成[自訂]主題
+ 儲存
+ 丟棄
+ 恢復變更
+ 確定要恢復變更嗎?
+ 尚未儲存變更。離開前是否儲存?
+ 將顏色套用於[簡易]系列全部應用程式
+ 顏色更新成功。新增了一個名為 \'Shared\' 的新主題,以後請使用那個來更新全部應用程式顏色。
+
+ Simple Thank You 來解鎖此功能及支持開發者。謝謝!
+ ]]>
+
+
+
+ 明亮主題
+ 灰暗主題
+ Solarized
+ 深紅主題
+ 黑白主題
+ 自訂
+ 分享
+
+
+ 更新了什麼
+ *此處僅列出大更新,尚有一些小改善
+
+
+ 刪除
+ 移除
+ 重新命名
+ 分享
+ 分享方式
+ 全選
+ 隱藏
+ 取消隱藏
+ 隱藏資料夾
+ 取消隱藏資料夾
+ 暫時顯示隱藏的檔案
+ 停止顯示隱藏的檔案
+ 你不能一次分享這麼多內容
+ 清空並停用回收桶
+ 復原
+ 取消復原
+
+
+ 排序
+ 名稱
+ 大小
+ 最後修改
+ 拍照日期
+ 標題
+ 檔案名稱
+ 副檔名
+ 遞增
+ 遞減
+ 僅套用於此資料夾
+
+
+ 確定要進行刪除嗎?
+ 你確定要刪除%s嗎?
+ 你確定要把%s移到回收桶嗎?
+ 你確定要刪除這個項目嗎?
+ 你確定要把這個項目移到回收桶嗎?
+ 這情況不再詢問
+ 是
+ 否
+
+
+ - 注意: 你正在刪除1個資料夾
+ - 注意: 你正在刪除%d個資料夾
+
+
+
+ PIN碼
+ 輸入PIN碼
+ 請輸入PIN碼
+ PIN碼錯誤
+ 再次輸入PIN碼
+ 圖形
+ 畫出解鎖圖形
+ 解鎖圖形錯誤
+ 再次畫出解鎖圖形
+ 指紋
+ 添加指紋
+ 請將您的手指放置在指紋感應器上
+ 驗證失敗
+ 驗證被限制,請稍後再重試
+ 你尚未有登錄的指紋,請在裝置的[設定]內添加
+ 前往[設定]
+ 密碼設置成功。若遺忘了,請重新安裝應用程式。
+ 保護設置成功。若再次設置時發生問題,請重新安裝應用程式。
+
+
+ 昨天
+ 今天
+ 明天
+ 秒鐘
+ 分鐘
+ 小時
+ 天
+
+
+ - %d秒鐘
+ - %d秒鐘
+
+
+ - %d分鐘
+ - %d分鐘
+
+
+ - %d小時
+ - %d小時
+
+
+ - %d天
+ - %d天
+
+
+ - %d週
+ - %d週
+
+
+ - %d個月
+ - %d個月
+
+
+ - %d年
+ - %d年
+
+
+
+
+ - %d秒鐘
+ - %d秒鐘
+
+
+ - %d分鐘
+ - %d分鐘
+
+
+ - %d小時
+ - %d小時
+
+
+
+
+ - %d秒鐘前
+ - %d秒鐘前
+
+
+ - %d分鐘前
+ - %d分鐘前
+
+
+ - %d小時前
+ - %d小時前
+
+
+ - %d天前
+ - %d天前
+
+
+
+
+ - %d秒鐘
+ - %d秒鐘
+
+
+ - %d分鐘
+ - %d分鐘
+
+
+ - %d小時
+ - %d小時
+
+
+
+ 鬧鐘響起剩餘時間:\n%s
+ 提醒觸發剩餘時間:\n%s
+ 依靠鬧鐘功能之前,請先確認是否正常運作,有可能因為省電相關的系統限制而導致失靈。
+ 依靠提醒功能之前,請先確認是否正常運作,有可能因為省電相關的系統限制而導致失靈。
+
+
+ 鬧鐘
+ 延遲
+ 關閉
+ 不提醒
+ 準時
+ 系統音效
+ 你的音效
+ 添加新的音效
+ 無音效
+
+
+ 設定
+ 購買Simple Thank You
+ 自訂顏色
+ 自訂小工具顏色
+ 強制使用英語
+ 啟動時不顯示更新了什麼
+ 顯示隱藏的項目
+ 字體大小
+ 小
+ 中
+ 大
+ 特大
+ 用密碼保護隱藏的物件
+ 用密碼保護整個應用程式
+ 當檔案複製/移動/重新命名時,保留舊的[最後修改]數值
+ 拖曳滾動條時,顯示訊息氣泡
+ 當程式在前景使用時,防止手機進入睡眠
+ 刪除不再進行確認
+ 啟用頂端下拉更新
+ 使用24小時制
+ 每周由星期日開始
+ 小工具
+ 總是使用相同延遲間隔
+ 延遲時間
+ 按下按鈕時震動
+ 將項目移入回收桶取代刪除
+ 回收桶清理間隔
+ 清空回收桶
+ 強制直向模式
+
+
+ 可見度
+ 安全性
+ 滑動
+ 檔案操作
+ 回收桶
+ 儲存中
+ 啟動
+ 文字
+
+
+ 還原這個檔案
+ 還原選擇的檔案
+ 還原全部檔案
+ 回收桶已成功清空
+ 檔案已成功還原
+ 你確定要清空回收桶嗎?檔案將會永久消失。
+ 回收桶是空的
+
+
+ 正在匯入…
+ 正在匯出…
+ 匯入成功
+ 匯出成功
+ 匯入成功
+ 匯出失敗
+ 部分項目匯入失敗
+ 部分項目匯出失敗
+ 未發現供匯出的項目
+
+
+ OTG
+ 請在下個畫面選擇OTG裝置的根目錄,以授予存取權限
+ 選擇到錯誤資料夾,請選擇你OTG裝置的根目錄
+
+
+ 一月
+ 二月
+ 三月
+ 四月
+ 五月
+ 六月
+ 七月
+ 八月
+ 九月
+ 十月
+ 十一月
+ 十二月
+
+
+ 在一月
+ 在二月
+ 在三月
+ 在四月
+ 在五月
+ 在六月
+ 在七月
+ 在八月
+ 在九月
+ 在十月
+ 在十一月
+ 在十二月
+
+ 星期一
+ 星期二
+ 星期三
+ 星期四
+ 星期五
+ 星期六
+ 星期日
+
+ 一
+ 二
+ 三
+ 四
+ 五
+ 六
+ 日
+
+ 週一
+ 週二
+ 週三
+ 週四
+ 週五
+ 週六
+ 週日
+
+
+ 關於
+ 觀看原始碼
+ 傳送您的反饋或建議
+ 更多應用程式
+ 第三方授權
+ 邀請朋友
+ 嘿,來試試看「%1$s」吧! %2$s
+ 邀請
+ 評分我們
+ 捐贈
+ 捐贈
+ 追蹤我們
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ 進階資訊
+ 應用程式版本: %s
+ 裝置系統: %s
+
+
+ 希望您喜愛這應用程式。 它不含廣告,所以請購買應用程式 Simple Thank You 來支持開發者,這對話框也不會再出現。
+ 感謝!
+ ]]>
+
+ 購買
+ 請更新[Simple Thank You]至最新版本
+ 在您發問前,請先閱讀[常見問題],或許解決方法就在那裡。
+ 讀它
+
+
+
+
+ 告訴你最近有一個新的應用程式剛發布:
+ %2$s
+ 點標題下載來試試看吧
+ 謝謝
+ ]]>
+
+
+
+ 常見問題
+ 在你發問之前,請先閱讀
+ 為什麼我在小工具列表中沒看到這應用程式的小工具?
+ 這很可能是因為你把應用程式移動到SD卡。那樣的話,有個隱藏特定程式小工具的Android系統限制存在。
+ 只有一個解決方法,就是經由你的裝置設定,把應用程式移動回內部儲存空間。
+ 我想要支持您,但我無法捐款。還有什麼我能做的嗎?
+ 當然好囉。你可以推廣關於這應用程式,或是給予好的反饋和評分。你也可以幫忙把這應用程式翻譯成新的語言,或者只更新一些現有的翻譯。
+ 你可以在 https://github.com/SimpleMobileTools/General-Discussion 找到教學,或者若你需要幫忙,只要發個訊息到 hello@simplemobiletools.com。
+ 我誤刪了一些檔案,如何復原?
+ 不幸地,沒有辦法。檔案經確認後就立即刪除了,沒有垃圾筒可用。
+ 我不喜歡小工具的顏色,我能更改嗎?
+ 可啊,當你在主畫面拖動小工具時,會出現小工具設置畫面。你會在左下角看到顏色方塊,點下去再挑個新顏色就好了。你也能用拉條來調整透明度。
+ 我有辦法恢復被刪除的檔案嗎?
+ 如果它們確實被刪除了,那沒辦法。然而,你可以在程式設定中啟用回收桶來取代刪除。那只會移動檔案而不是刪除它們。
+
+
+ 這程式使用了下列的第三方函式庫來讓我工作簡化。感謝。
+ 第三方授權
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values/arrays.xml b/commons/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..7939099
--- /dev/null
+++ b/commons/src/main/res/values/arrays.xml
@@ -0,0 +1,274 @@
+
+
+
+ - @color/md_red
+ - @color/md_pink
+ - @color/md_purple
+ - @color/md_deep_purple
+ - @color/md_indigo
+ - @color/md_blue
+ - @color/md_light_blue
+ - @color/md_cyan
+ - @color/md_teal
+ - @color/md_green
+ - @color/md_light_green
+ - @color/md_lime
+ - @color/md_yellow
+ - @color/md_amber
+ - @color/md_orange
+ - @color/md_deep_orange
+ - @color/md_brown
+ - @color/md_blue_grey
+ - @color/md_grey
+
+
+
+ - @color/md_red_700
+ - @color/md_pink_700
+ - @color/md_purple_700
+ - @color/md_deep_purple_700
+ - @color/md_indigo_700
+ - @color/md_blue_700
+ - @color/md_light_blue_700
+ - @color/md_cyan_700
+ - @color/md_teal_700
+ - @color/md_green_700
+ - @color/md_light_green_700
+ - @color/md_lime_700
+ - @color/md_yellow_700
+ - @color/md_amber_700
+ - @color/md_orange_700
+ - @color/md_deep_orange_700
+ - @color/md_brown_700
+ - @color/md_blue_grey_700
+ - @color/md_grey_black
+
+
+
+ - @color/md_amber_100
+ - @color/md_amber_200
+ - @color/md_amber_300
+ - @color/md_amber_400
+ - @color/md_amber_500
+ - @color/md_amber_600
+ - @color/md_amber_700
+ - @color/md_amber_800
+ - @color/md_amber_900
+
+
+
+ - @color/md_blue_100
+ - @color/md_blue_200
+ - @color/md_blue_300
+ - @color/md_blue_400
+ - @color/md_blue_500
+ - @color/md_blue_600
+ - @color/md_blue_700
+ - @color/md_blue_800
+ - @color/md_blue_900
+
+
+
+ - @color/md_blue_grey_100
+ - @color/md_blue_grey_200
+ - @color/md_blue_grey_300
+ - @color/md_blue_grey_400
+ - @color/md_blue_grey_500
+ - @color/md_blue_grey_600
+ - @color/md_blue_grey_700
+ - @color/md_blue_grey_800
+ - @color/md_blue_grey_900
+
+
+
+ - @color/md_brown_100
+ - @color/md_brown_200
+ - @color/md_brown_300
+ - @color/md_brown_400
+ - @color/md_brown_500
+ - @color/md_brown_600
+ - @color/md_brown_700
+ - @color/md_brown_800
+ - @color/md_brown_900
+
+
+
+ - @color/md_cyan_100
+ - @color/md_cyan_200
+ - @color/md_cyan_300
+ - @color/md_cyan_400
+ - @color/md_cyan_500
+ - @color/md_cyan_600
+ - @color/md_cyan_700
+ - @color/md_cyan_800
+ - @color/md_cyan_900
+
+
+
+ - @color/md_deep_orange_100
+ - @color/md_deep_orange_200
+ - @color/md_deep_orange_300
+ - @color/md_deep_orange_400
+ - @color/md_deep_orange_500
+ - @color/md_deep_orange_600
+ - @color/md_deep_orange_700
+ - @color/md_deep_orange_800
+ - @color/md_deep_orange_900
+
+
+
+ - @color/md_deep_purple_100
+ - @color/md_deep_purple_200
+ - @color/md_deep_purple_300
+ - @color/md_deep_purple_400
+ - @color/md_deep_purple_500
+ - @color/md_deep_purple_600
+ - @color/md_deep_purple_700
+ - @color/md_deep_purple_800
+ - @color/md_deep_purple_900
+
+
+
+ - @color/md_green_100
+ - @color/md_green_200
+ - @color/md_green_300
+ - @color/md_green_400
+ - @color/md_green_500
+ - @color/md_green_600
+ - @color/md_green_700
+ - @color/md_green_800
+ - @color/md_green_900
+
+
+
+ - @color/md_grey_white
+ - @color/md_grey_200
+ - @color/md_grey_300
+ - @color/md_grey_400
+ - @color/md_grey_500
+ - @color/md_grey_600
+ - @color/md_grey_700
+ - @color/md_grey_800
+ - @color/md_grey_black
+
+
+
+ - @color/md_indigo_100
+ - @color/md_indigo_200
+ - @color/md_indigo_300
+ - @color/md_indigo_400
+ - @color/md_indigo_500
+ - @color/md_indigo_600
+ - @color/md_indigo_700
+ - @color/md_indigo_800
+ - @color/md_indigo_900
+
+
+
+ - @color/md_light_blue_100
+ - @color/md_light_blue_200
+ - @color/md_light_blue_300
+ - @color/md_light_blue_400
+ - @color/md_light_blue_500
+ - @color/md_light_blue_600
+ - @color/md_light_blue_700
+ - @color/md_light_blue_800
+ - @color/md_light_blue_900
+
+
+
+ - @color/md_light_green_100
+ - @color/md_light_green_200
+ - @color/md_light_green_300
+ - @color/md_light_green_400
+ - @color/md_light_green_500
+ - @color/md_light_green_600
+ - @color/md_light_green_700
+ - @color/md_light_green_800
+ - @color/md_light_green_900
+
+
+
+ - @color/md_lime_100
+ - @color/md_lime_200
+ - @color/md_lime_300
+ - @color/md_lime_400
+ - @color/md_lime_500
+ - @color/md_lime_600
+ - @color/md_lime_700
+ - @color/md_lime_800
+ - @color/md_lime_900
+
+
+
+ - @color/md_orange_100
+ - @color/md_orange_200
+ - @color/md_orange_300
+ - @color/md_orange_400
+ - @color/md_orange_500
+ - @color/md_orange_600
+ - @color/md_orange_700
+ - @color/md_orange_800
+ - @color/md_orange_900
+
+
+
+ - @color/md_pink_100
+ - @color/md_pink_200
+ - @color/md_pink_300
+ - @color/md_pink_400
+ - @color/md_pink_500
+ - @color/md_pink_600
+ - @color/md_pink_700
+ - @color/md_pink_800
+ - @color/md_pink_900
+
+
+
+ - @color/md_purple_100
+ - @color/md_purple_200
+ - @color/md_purple_300
+ - @color/md_purple_400
+ - @color/md_purple_500
+ - @color/md_purple_600
+ - @color/md_purple_700
+ - @color/md_purple_800
+ - @color/md_purple_900
+
+
+
+ - @color/md_red_100
+ - @color/md_red_200
+ - @color/md_red_300
+ - @color/md_red_400
+ - @color/md_red_500
+ - @color/md_red_600
+ - @color/md_red_700
+ - @color/md_red_800
+ - @color/md_red_900
+
+
+
+ - @color/md_teal_100
+ - @color/md_teal_200
+ - @color/md_teal_300
+ - @color/md_teal_400
+ - @color/md_teal_500
+ - @color/md_teal_600
+ - @color/md_teal_700
+ - @color/md_teal_800
+ - @color/md_teal_900
+
+
+
+ - @color/md_yellow_100
+ - @color/md_yellow_200
+ - @color/md_yellow_300
+ - @color/md_yellow_400
+ - @color/md_yellow_500
+ - @color/md_yellow_600
+ - @color/md_yellow_700
+ - @color/md_yellow_800
+ - @color/md_yellow_900
+
+
diff --git a/commons/src/main/res/values/colors.xml b/commons/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c7bbc44
--- /dev/null
+++ b/commons/src/main/res/values/colors.xml
@@ -0,0 +1,432 @@
+
+
+ #FFF57C00
+ #FFD76D00
+ @color/color_primary
+
+ #08000000
+ #44888888
+ #40808080
+ #CC000000
+
+
+ @color/md_grey_600
+ @color/md_grey_white
+ @color/md_grey_200
+ @color/md_grey_800
+ @color/theme_dark_text_color
+ @color/theme_dark_background_color
+ @color/md_indigo_900_dark
+ @color/md_amber_700
+ @color/md_indigo_900
+ @color/md_red_700
+
+
+ #FFECECEC
+ #FFB2B2B2
+
+
+ #FF757575
+
+
+ #FFC107
+ #2196F3
+ #607D8B
+ #795548
+ #00BCD4
+ #FF5722
+ #673AB7
+ #4CAF50
+ #9E9E9E
+ #3F51B5
+ #03A9F4
+ #8BC34A
+ #CDDC39
+ #FF9800
+ #E91E63
+ #9C27B0
+ #F44336
+ #009688
+ #FFEB3B
+
+ #FFECB3
+ #FFE082
+ #FFD54F
+ #FFCA28
+ #FFC107
+ #FFB300
+ #FFA000
+ #FF8F00
+ #FF6F00
+
+ #FFE28A
+ #FFD659
+ #FFCC26
+ #FFC100
+ #DEA700
+ #D79700
+ #D78700
+ #D77800
+ #D75D00
+
+ #BBDEFB
+ #90CAF9
+ #64B5F6
+ #42A5F5
+ #2196F3
+ #1E88E5
+ #1976D2
+ #1565C0
+ #0D47A1
+
+ #94CCF9
+ #69B8F7
+ #3DA2F4
+ #1A92F3
+ #0B82E0
+ #1673C4
+ #1462AE
+ #11529B
+ #09367B
+
+ #CFD8DC
+ #B0BBC5
+ #90A4AE
+ #78909C
+ #607D8B
+ #546E7A
+ #455A64
+ #37474F
+ #263238
+
+ #B8C5CB
+ #99A7B4
+ #78919D
+ #647C88
+ #4F6873
+ #445962
+ #34454C
+ #263237
+ #151C1F
+
+ #D7CCC8
+ #BCAAA4
+ #A1887F
+ #8D6E63
+ #795548
+ #6D4C41
+ #5D4037
+ #4E342E
+ #3E2723
+
+ #C6B7B1
+ #AB958D
+ #8F7369
+ #755B52
+ #5F4339
+ #533A31
+ #432E28
+ #34231F
+ #241714
+
+ #B2EBF2
+ #80DEEA
+ #4DD0E1
+ #26C6DA
+ #00BCD4
+ #00ACC1
+ #0097A7
+ #00838F
+ #006064
+
+ #90E3ED
+ #5DD5E5
+ #2AC7DB
+ #1FA7B8
+ #0098AB
+ #008898
+ #00727E
+ #005E66
+ #00393B
+
+ #FFCCBC
+ #FFAB91
+ #FF8A65
+ #FF7043
+ #FF5722
+ #F4511E
+ #E64A19
+ #D84315
+ #BF360C
+
+ #FFAD93
+ #FF8C68
+ #FF6B3C
+ #FF511A
+ #F93C00
+ #DF3D0A
+ #C13E14
+ #B33710
+ #992B09
+
+ #D1C4E9
+ #B39DDB
+ #9575CD
+ #7E57C2
+ #673AB7
+ #5E35B1
+ #512DA8
+ #4527A0
+ #311B92
+
+ #BAA6DE
+ #9C7FD0
+ #7E56C2
+ #693FB0
+ #563098
+ #4E2B92
+ #412488
+ #371F7F
+ #251470
+
+ #C8E6C9
+ #A5D6A7
+ #81C784
+ #66BB6A
+ #4CAF50
+ #43A047
+ #388E3C
+ #2E7D32
+ #1B5E20
+
+ #ACDAAE
+ #89CA8D
+ #65BB69
+ #4CAC51
+ #409343
+ #37833A
+ #2C7130
+ #235F26
+ #113E15
+
+ #FFFFFF
+ #EEEEEE
+ #E0E0E0
+ #BDBDBD
+ #9E9E9E
+ #757575
+ #616161
+ #424242
+ #000000
+
+ #DFDFDF
+ #DADADA
+ #CCCCCC
+ #A9A9A9
+ #8A8A8A
+ #606060
+ #4C4C4C
+ #2D2D2D
+ #000000
+
+ #C5CAE9
+ #9FA8DA
+ #7986CB
+ #5C6BC0
+ #3F51B5
+ #3949AB
+ #303F9F
+ #283593
+ #1A237E
+
+ #A8AFDE
+ #828ECF
+ #5B6CC0
+ #4354B0
+ #344497
+ #2E3C8C
+ #263380
+ #1F2973
+ #12195C
+
+ #B3E5FC
+ #81D4fA
+ #4fC3F7
+ #29B6FC
+ #03A9F4
+ #039BE5
+ #0288D1
+ #0277BD
+ #01579B
+
+ #8BD8FB
+ #59C7F9
+ #27B6F6
+ #02A7F9
+ #028DCC
+ #0280BD
+ #016EA9
+ #015E95
+ #004072
+
+ #DCEDC8
+ #C5E1A5
+ #AED581
+ #9CCC65
+ #8BC34A
+ #7CB342
+ #689F38
+ #558B2F
+ #33691E
+
+ #C9E3A9
+ #B2D787
+ #9BCB62
+ #89C246
+ #76AC38
+ #679537
+ #54812D
+ #426C24
+ #234915
+
+ #F0F4C3
+ #E6EE9C
+ #DCE775
+ #D4E157
+ #CDDC39
+ #C0CA33
+ #A4B42B
+ #9E9D24
+ #827717
+
+ #E8EEA0
+ #DEE879
+ #D3E152
+ #CBDB34
+ #BAC923
+ #A2AA2A
+ #869323
+ #7D7D1C
+ #5F5710
+
+ #FFE0B2
+ #FFCC80
+ #FFB74D
+ #FFA726
+ #FF9800
+ #FB8C00
+ #F57C00
+ #EF6C00
+ #E65100
+
+ #FFD089
+ #FFBC57
+ #FFA724
+ #FD9600
+ #D78000
+ #D37600
+ #CD6800
+ #C65A00
+ #BD4200
+
+ #F8BBD0
+ #F48FB1
+ #F06292
+ #EC407A
+ #E91E63
+ #D81B60
+ #C2185B
+ #AD1457
+ #880E4F
+
+ #F596B7
+ #F16998
+ #ED3C78
+ #E91A60
+ #CB1352
+ #B4154F
+ #9E134A
+ #880F44
+ #630A3A
+
+ #E1BEE7
+ #CE93D8
+ #BA68C8
+ #AB47BC
+ #9C27B0
+ #8E24AA
+ #7B1FA2
+ #6A1B9A
+ #4A148C
+
+ #D3A0DC
+ #C175CD
+ #AC4ABD
+ #913AA0
+ #7F1F8F
+ #711C88
+ #611880
+ #521477
+ #370E68
+
+ #FFCDD2
+ #EF9A9A
+ #E57373
+ #EF5350
+ #F44336
+ #E53935
+ #D32F2F
+ #C62828
+ #B71C1C
+
+ #FFA4AE
+ #EA7777
+ #DF5050
+ #EC2E2A
+ #F21F0F
+ #D61F1A
+ #B32525
+ #A42020
+ #941616
+
+ #B2DFDB
+ #80CBC4
+ #4DB6AC
+ #26A69A
+ #009688
+ #00897B
+ #00796B
+ #00695C
+ #004D40
+
+ #95D3CE
+ #63BFB7
+ #3F9B92
+ #1E857C
+ #006D63
+ #006056
+ #005047
+ #004038
+ #00241E
+
+ #FFF9C4
+ #FFF590
+ #FFF176
+ #FFEE58
+ #FFEB3B
+ #FDD835
+ #FBC02D
+ #F9A825
+ #F57F17
+
+ #FFF59B
+ #FFF267
+ #FFED4D
+ #FFEA2F
+ #FFE712
+ #FDD10B
+ #FBB504
+ #EF9606
+ #DA6B09
+
+
diff --git a/commons/src/main/res/values/dimens.xml b/commons/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..f0dc94b
--- /dev/null
+++ b/commons/src/main/res/values/dimens.xml
@@ -0,0 +1,40 @@
+
+ 2dp
+ 4dp
+ 8dp
+ 12dp
+ 16dp
+ 20dp
+ 24dp
+
+ 40dp
+ 30dp
+ 240dp
+ 272dp
+ 30dp
+ 30dp
+ 70dp
+
+ 48dp
+ 8dp
+ 40dp
+ 72dp
+ 30dp
+
+ 56dp
+ 26dp
+ 50dp
+ 80dp
+ 10dp
+ 64dp
+
+ 8sp
+ 10sp
+ 12sp
+ 14sp
+ 16sp
+ 17sp
+ 18sp
+ 20sp
+ 22sp
+
diff --git a/commons/src/main/res/values/donottranslate.xml b/commons/src/main/res/values/donottranslate.xml
new file mode 100644
index 0000000..4ccbf1d
--- /dev/null
+++ b/commons/src/main/res/values/donottranslate.xml
@@ -0,0 +1,123 @@
+
+ Simple Commons
+ %1$s / %2$s
+ https://simplemobiletools.com/donate
+ https://play.google.com/store/apps/details?id=com.simplemobiletools.thankyou
+ a.m.
+ p.m.
+
+
+ %1$s%2$s
+ \nhttps://simplemobiletools.com
+ hello@simplemobiletools.com
+
+
+ Copyright 2010 - 2018 JetBrains s.r.o.\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/JetBrains/kotlin
+ Copyright 2018 David Morrissey\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/davemorrissey/subsampling-scale-image-view
+ Copyright 2014 Google, Inc. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY GOOGLE, INC. ``AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ https://github.com/bumptech/glide
+ Copyright 2013 Square, Inc.\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/square/picasso
+ Copyright 2016, Arthur Teplitzki, 2013, Edmodo, Inc.\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with the License. You may obtain a copy of the License in the LICENSE file, or at:\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/ArthurHub/Android-Image-Cropper
+ The MIT License (MIT)\n\nCopyright (c) 2014 Big Nerd Ranch\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: \n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. \n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ https://github.com/bignerdranch/recyclerview-multiselect
+ Copyright 2015 Diego Gómez Olvera\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/diego-gomez-olvera/RtlViewPager
+ Copyright 2013 JodaOrg\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/JodaOrg/joda-time
+ BSD License\n\nFor Stetho software\n\nCopyright (c) 2015, Facebook, Inc. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice, this\n list of conditions and the following disclaimer.\n\n * Redistributions in binary form must reproduce the above copyright notice,\n this list of conditions and the following disclaimer in the documentation\n and/or other materials provided with the distribution.\n\n * Neither the name Facebook nor the names of its contributors may be used to\n endorse or promote products derived from this software without specific\n prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ https://github.com/facebook/stetho
+ Copyright 2012 Square, Inc.\nCopyright 2010 Google, Inc.\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/square/otto
+ Copyright 2017 Chris Banes\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/chrisbanes/PhotoView
+ Copyright 2017 aritraroy\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/aritraroy/PatternLockView
+ Copyright 2015 - 2017 AJ Alt\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/ajalt/reprint
+ The MIT License (MIT)\n\nCopyright (c) 2016 Karol Wrótniak, Droids on Roids\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: \n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. \n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ https://github.com/koral--/android-gif-drawable
+ Copyright 2014 Grantland Chew\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/grantland/android-autofittextview
+ The MIT License\n\nCopyright (c) 2010 Xtreme Labs and Pivotal Labs\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ https://github.com/robolectric/robolectric
+ Copyright 2014 The Android Open Source Project\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://developer.android.com/training/testing/espresso/index.html
+ Copyright 2008 Google Inc.\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/google/gson
+ Copyright 2015 Square, Inc.\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/square/leakcanary
+ The MIT License (MIT)\n\nCopyright (c) 2018 ShawnLin013\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: \n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. \n\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ https://github.com/ShawnLin013/NumberPicker
+ Copyright 2014 Google, Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/google/ExoPlayer
+ Copyright 2016 Google, Inc. All rights reserved.\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/googlevr/gvr-android-sdk
+ Copyright 2016 The Apache Software Foundation\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions and limitations under the License.
+ https://mvnrepository.com/artifact/org.apache.sanselan/sanselan/0.97-incubator
+ Copyright 2016 ravi8x\n\nLicensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.You may obtain a copy of the License at\n\nhttps://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.See the License for the specific language governing permissions and limitations under the License.
+ https://github.com/ravi8x/AndroidPhotoFilters
+
+
+ - @string/january
+ - @string/february
+ - @string/march
+ - @string/april
+ - @string/may
+ - @string/june
+ - @string/july
+ - @string/august
+ - @string/september
+ - @string/october
+ - @string/november
+ - @string/december
+
+
+
+ - @string/in_january
+ - @string/in_february
+ - @string/in_march
+ - @string/in_april
+ - @string/in_may
+ - @string/in_june
+ - @string/in_july
+ - @string/in_august
+ - @string/in_september
+ - @string/in_october
+ - @string/in_november
+ - @string/in_december
+
+
+
+ - @string/monday
+ - @string/tuesday
+ - @string/wednesday
+ - @string/thursday
+ - @string/friday
+ - @string/saturday
+ - @string/sunday
+
+
+
+ - @string/monday_letter
+ - @string/tuesday_letter
+ - @string/wednesday_letter
+ - @string/thursday_letter
+ - @string/friday_letter
+ - @string/saturday_letter
+ - @string/sunday_letter
+
+
+
+ - @string/monday_short
+ - @string/tuesday_short
+ - @string/wednesday_short
+ - @string/thursday_short
+ - @string/friday_short
+ - @string/saturday_short
+ - @string/sunday_short
+
+
diff --git a/commons/src/main/res/values/ids.xml b/commons/src/main/res/values/ids.xml
new file mode 100644
index 0000000..9d94707
--- /dev/null
+++ b/commons/src/main/res/values/ids.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/commons/src/main/res/values/integers.xml b/commons/src/main/res/values/integers.xml
new file mode 100644
index 0000000..4e8d555
--- /dev/null
+++ b/commons/src/main/res/values/integers.xml
@@ -0,0 +1,4 @@
+
+
+ 1
+
diff --git a/commons/src/main/res/values/strings.xml b/commons/src/main/res/values/strings.xml
new file mode 100644
index 0000000..1bdd5fc
--- /dev/null
+++ b/commons/src/main/res/values/strings.xml
@@ -0,0 +1,561 @@
+
+ OK
+ Cancel
+ Save as
+ File saved successfully
+ Invalid file format
+ Out of memory error
+ An error occurred: %s
+ Open with
+ No valid app found
+ Set as
+ Value copied to clipboard
+ Unknown
+ Always
+ Never
+ Details
+ Notes
+ Deleting folder \'%s\'
+ None
+ Label
+
+
+ Favorites
+ Add favorites
+ Add to favorites
+ Remove from favorites
+
+
+ Search
+ Type in at least 2 characters to start the search.
+
+
+ Filter
+ No items found.
+ Change filter
+
+
+ Storage permission is required
+ Contacts permission is required
+ Camera permission is required
+ Audio permission is required
+
+
+ Rename file
+ Rename folder
+ Could not rename the file
+ Could not rename the folder
+ Folder name must not be empty
+ A folder with that name already exists
+ Cannot rename the root folder of a storage
+ Folder renamed successfully
+ Renaming folder
+ Filename cannot be empty
+ Filename contains invalid characters
+ Filename \'%s\' contains invalid characters
+ Extension cannot be empty
+ Source file %s doesn\'t exist
+
+
+ Copy
+ Move
+ Copy / Move
+ Copy to
+ Move to
+ Source
+ Destination
+ Select destination
+ Click here to select destination
+ Could not write to the selected destination
+ Please select a destination
+ Source and destination cannot be the same
+ Could not copy the files
+ Copying…
+ Files copied successfully
+ An error occurred
+ Moving…
+ Files moved successfully
+ Some files could not be moved
+ Some files could not be copied
+ No files selected
+ Saving…
+ Could not create folder %s
+ Could not create file %s
+
+
+ Create new
+ Folder
+ File
+ Create new folder
+ A file or folder with that name already exists
+ The name contains invalid characters
+ Please enter a name
+ An unknown error occurred
+
+
+ File \"%s\" already exists
+ File \"%s\" already exists. Overwrite?
+ Folder \"%s\" already exists
+ Merge
+ Overwrite
+ Skip
+ Append with \'_1\'
+ Apply to all
+
+
+ Select a folder
+ Select a file
+ Confirm external storage access
+ Please choose the root folder of the SD card on the next screen, to grant write access
+ If you don\'t see the SD card, try this
+ Confirm selection
+
+
+ - %d item
+ - %d items
+
+
+
+
+ - %d item
+ - %d items
+
+
+
+ - Deleting %d item
+ - Deleting %d items
+
+
+
+ Select storage
+ Internal
+ SD Card
+ Root
+ Wrong folder selected, please select the root folder of your SD card
+ SD card and OTG device paths cannot be the same
+ You seem to have the app installed on an SD card, that makes the app widgets unavailable. You won\'t even see them on the list of available widgets.
+ It is a system limitation, so if you want to use the widgets, you have to move the app back on the internal storage.
+
+
+ Properties
+ Path
+ Items selected
+ Direct children count
+ Total files count
+ Resolution
+ Duration
+ Artist
+ Album
+ Focal length
+ Exposure time
+ ISO speed
+ F-number
+ Camera
+ EXIF
+ Song title
+
+
+ Background color
+ Text color
+ Primary color
+ Foreground color
+ App icon color
+ Restore defaults
+ Change color
+ Theme
+ Changing a color will make it switch to Custom theme
+ Save
+ Discard
+ Undo changes
+ Are you sure you want to undo your changes?
+ You have unsaved changes. Save before exit?
+ Apply colors to all Simple Apps
+ Colors updated successfully. A new Theme called \'Shared\' has been added, please use that for updating all app colors in the future.
+
+ Simple Thank You to unlock this function and support the development. Thanks!
+ ]]>
+
+
+
+ Light
+ Dark
+ Solarized
+ Dark red
+ Black & White
+ Custom
+ Shared
+
+
+ What\'s new
+ * only the bigger updates are listed here, there are always some smaller improvements too
+
+
+ Delete
+ Remove
+ Rename
+ Share
+ Share via
+ Select all
+ Hide
+ Unhide
+ Hide folder
+ Unhide folder
+ Temporarily show hidden
+ Stop showing hidden media
+ You cannot share this much content at once
+ Empty and disable the Recycle Bin
+ Undo
+ Redo
+
+
+ Sort by
+ Name
+ Size
+ Last modified
+ Date taken
+ Title
+ Filename
+ Extension
+ Ascending
+ Descending
+ Use for this folder only
+
+
+ Are you sure you want to proceed with the deletion?
+ Are you sure you want to delete %s?
+ Are you sure you want to move %s into the Recycle Bin?
+ Are you sure you want to delete this item?
+ Are you sure you want to move this item into the Recycle Bin?
+ Do not ask again in this session
+ Yes
+ No
+
+
+ - WARNING: You are deleting a folder
+ - WARNING: You are deleting %d folders
+
+
+
+ PIN
+ Enter PIN
+ Please enter a PIN
+ Wrong PIN
+ Repeat PIN
+ Pattern
+ Insert pattern
+ Wrong pattern
+ Repeat pattern
+ Fingerprint
+ Add fingerprint
+ Please place your finger on the fingerprint sensor
+ Authentication failed
+ Authentication blocked, please try again in a moment
+ You have no fingerprints registered, please add some in the Settings of your device
+ Go to Settings
+ Password setup successfully. Please reinstall the app in case you forget it.
+ Protection setup successfully. Please reinstall the app in case of problems with reseting it.
+
+
+ Yesterday
+ Today
+ Tomorrow
+ seconds
+ minutes
+ hours
+ days
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+ - %d day
+ - %d days
+
+
+ - %d week
+ - %d weeks
+
+
+ - %d month
+ - %d months
+
+
+ - %d year
+ - %d years
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+
+ - %d second before
+ - %d seconds before
+
+
+ - %d minute before
+ - %d minutes before
+
+
+ - %d hour before
+ - %d hours before
+
+
+ - %d day before
+ - %d days before
+
+
+
+
+ - %d second
+ - %d seconds
+
+
+ - %d minute
+ - %d minutes
+
+
+ - %d hour
+ - %d hours
+
+
+
+ Time remaining till the alarm goes off:\n%s
+ Time remaining till the reminder triggers:\n%s
+ Please make sure the alarm works properly before relying on it. It could misbehave due to system restrictions related to battery saving.
+ Please make sure the reminders work properly before relying on them. They could misbehave due to system restrictions related to battery saving.
+
+
+ Alarm
+ Snooze
+ Dismiss
+ No reminder
+ At start
+ System sounds
+ Your sounds
+ Add a new sound
+ No sound
+
+
+ Settings
+ Purchase Simple Thank You
+ Customize colors
+ Customize widget colors
+ Use English language
+ Avoid showing What\'s New on startup
+ Show hidden items
+ Font size
+ Small
+ Medium
+ Large
+ Extra large
+ Password protect hidden item visibility
+ Password protect the whole application
+ Keep old last-modified value at file copy/move/rename
+ Show an info bubble at scrolling items by scrollbar dragging
+ Prevent phone from sleeping while the app is in foreground
+ Always skip delete confirmation dialog
+ Enable pull-to-refresh from the top
+ Use 24-hour time format
+ Start week on Sunday
+ Widgets
+ Always use same snooze time
+ Snooze time
+ Vibrate on button press
+ Move items into the Recycle Bin instead of deleting
+ Recycle Bin cleaning interval
+ Empty the Recycle Bin
+ Force portrait mode
+
+
+ Visibility
+ Security
+ Scrolling
+ File operations
+ Recycle Bin
+ Saving
+ Startup
+ Text
+
+
+ Restore this file
+ Restore selected files
+ Restore all files
+ The Recycle Bin has been emptied successfully
+ Files have been restored successfully
+ Are you sure you want to empty the Recycle Bin? The files will be permanently lost.
+ The Recycle Bin is empty
+
+
+ Importing…
+ Exporting…
+ Importing successful
+ Exporting successful
+ Importing failed
+ Exporting failed
+ Importing some entries failed
+ Exporting some entries failed
+ No entries for exporting have been found
+
+
+ OTG
+ Please choose the root folder of the OTG device on the next screen, to grant access
+ Wrong folder selected, please select the root folder of your OTG device
+
+
+ January
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+
+
+ in January
+ in February
+ in March
+ in April
+ in May
+ in June
+ in July
+ in August
+ in September
+ in October
+ in November
+ in December
+
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ Saturday
+ Sunday
+
+ M
+ T
+ W
+ T
+ F
+ S
+ S
+
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+
+
+ About
+ For the source codes visit
+ Send your feedback or suggestions to
+ More apps
+ Third party licences
+ Invite friends
+ Hey, come check out %1$s at %2$s
+ Invite via
+ Rate us
+ Donate
+ Donate
+ Follow us
+ v %1$s\nCopyright © Simple Mobile Tools %2$d
+ Additional info
+ App version: %s
+ Device OS: %s
+
+
+ hope you are enjoying the app. It contains no ads, please support its development by purchasing the Simple Thank You app, it will also prevent this dialog from showing up again.
+ Thank you!
+ ]]>
+
+ Purchase
+ Please update Simple Thank You to the latest version
+ Before you ask a question, please read the Frequently Asked Questions first, maybe the solution is there.
+ Read it
+
+
+
+
+ just letting you know that a new app has been released recently:
+ %2$s
+ You can download it by pressing the title.
+ Thanks
+ ]]>
+
+
+
+ Frequently asked questions
+ Before you ask a question, please first read the
+ How come I don\'t see this apps widget on the list of widgets?
+ It is most likely because you moved the app on an SD card. There is an Android system limitation which hides the given app widgets
+ in that case. The only solution is to move the app back onto the Internal Storage via your device settings.
+ I want to support you, but I cannot donate money. Is there anything else I can do?
+ Yes, of course. You can spread the word about the apps or give good feedback and ratings. You can also help by translating the apps in a new language, or just update some existing translations.
+ You can find the guide at https://github.com/SimpleMobileTools/General-Discussion , or just shoot me a message at hello@simplemobiletools.com if you need help.
+ I deleted some files by mistake, how can I recover them?
+ Sadly, you cannot. Files are deleted instantly after the confirmation dialog, there is no trashbin available.
+ I don\'t like the widget colors, can I change them?
+ Yep, as you drag a widget on your home screen a widget config screen appears. You will see colored squares at the bottom left corner, just press them to pick a new color. You can use the slider for adjusting the alpha too.
+ Can I somehow restore deleted files?
+ If they were really deleted, you cannot. However, you can enable using a Recycle Bin instead of deleting in the app settings. That will just move the files in it instead of deleting them.
+
+
+ This app uses the following third party libraries to make my life easier. Thank you.
+ Third party licences
+ Kotlin (programming language)
+ Subsampling Scale Image View (zoomable imageviews)
+ Glide (image loading and caching)
+ Picasso (image loading and caching)
+ Android Image Cropper (image crop and rotate)
+ RecyclerView MultiSelect (selecting multiple list items)
+ RtlViewPager (right to left swiping)
+ Joda-Time (Java date replacement)
+ Stetho (debugging databases)
+ Otto (event bus)
+ PhotoView (zoomable GIFs)
+ PatternLockView (pattern protection)
+ Reprint (fingerprint protection)
+ Gif Drawable (loading GIFs)
+ AutoFitTextView (resizing text)
+ Robolectric (testing framework)
+ Espresso (testing helper)
+ Gson (JSON parser)
+ Leak Canary (memory leak detector)
+ Number Picker (customizable number picker)
+ ExoPlayer (video player)
+ VR Panorama View (displaying panoramas)
+ Apache Sanselan (reading image metadata)
+ Android Photo Filters (image filters)
+
diff --git a/commons/src/main/res/values/styles.xml b/commons/src/main/res/values/styles.xml
new file mode 100644
index 0000000..8c852df
--- /dev/null
+++ b/commons/src/main/res/values/styles.xml
@@ -0,0 +1,1107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 3ae10ae..be16564 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Tue Sep 04 12:39:08 IST 2018
+#Sat Sep 08 20:52:16 EET 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
diff --git a/settings.gradle b/settings.gradle
index e58c753..41fb8f0 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-include ':app', ':whatsappprofileimage'
+include ':app', ':whatsappprofileimage', ':commons'
diff --git a/whatsappprofileimage/build.gradle b/whatsappprofileimage/build.gradle
index 26aac7f..7af8434 100644
--- a/whatsappprofileimage/build.gradle
+++ b/whatsappprofileimage/build.gradle
@@ -1,6 +1,7 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
-
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 28
@@ -26,14 +27,26 @@ android {
}
dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
-
- implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ //noinspection GradleCompatible
+ implementation 'com.android.support:appcompat-v7:28.0.0-rc02'
+ // implementation 'androidx.appcompat:appcompat:1.0.0-alpha3'
+ //noinspection GradleCompatible
+ implementation "com.android.support:support-core-utils:27.1.1"
+
+ implementation 'com.google.android.material:material:1.0.0-rc02'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
- compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'com.github.bumptech.glide:glide:4.8.0'
+ annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
+ kapt 'com.github.bumptech.glide:compiler:4.8.0'
+ implementation project(':commons')
+
+
}
+
repositories {
mavenCentral()
}
diff --git a/whatsappprofileimage/src/main/AndroidManifest.xml b/whatsappprofileimage/src/main/AndroidManifest.xml
index 8f3f047..9904257 100644
--- a/whatsappprofileimage/src/main/AndroidManifest.xml
+++ b/whatsappprofileimage/src/main/AndroidManifest.xml
@@ -1,2 +1,34 @@
+
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.mindorks.waprofileimage">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt
deleted file mode 100644
index 490bc50..0000000
--- a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/WAProfileImage.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package com.mindorks.waprofileimage
-
-class WAProfileImage
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/callback/TaskFinished.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/callback/TaskFinished.kt
new file mode 100644
index 0000000..e962897
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/callback/TaskFinished.kt
@@ -0,0 +1,6 @@
+package com.mindorks.waprofileimage.callback
+
+interface TaskFinished {
+ fun onTaskFinished(check: Boolean?)
+
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogBuilder.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogBuilder.kt
new file mode 100644
index 0000000..d3f3686
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogBuilder.kt
@@ -0,0 +1,415 @@
+package com.mindorks.waprofileimage.customdialog
+
+import android.app.Activity
+import android.content.Context
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Animation
+import android.view.animation.AnimationUtils
+import android.widget.BaseAdapter
+import android.widget.FrameLayout
+
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.customdialog.callback.OnBackPressListener
+import com.mindorks.waprofileimage.customdialog.callback.OnCancelListener
+import com.mindorks.waprofileimage.customdialog.callback.OnClickListener
+import com.mindorks.waprofileimage.customdialog.callback.OnDismissListener
+import com.mindorks.waprofileimage.customdialog.callback.OnItemClickListener
+import com.mindorks.waprofileimage.customdialog.dialogutil.Utils
+
+import java.util.Arrays
+
+class DialogBuilder {
+
+ private val margin = IntArray(4)
+ val contentPadding = IntArray(4)
+ private val outMostMargin = IntArray(4)
+ private val params = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.BOTTOM
+ )
+
+ private var adapter: BaseAdapter? = null
+ var context: Context? = null
+ private var footerView: View? = null
+ private var headerView: View? = null
+ private var holder: Holder? = null
+ private var gravity = Gravity.BOTTOM
+ private var onItemClickListener: OnItemClickListener? = null
+ private var onClickListener: OnClickListener? = null
+ private var onDismissListener: OnDismissListener? = null
+ private var onCancelListener: OnCancelListener? = null
+ private var onBackPressListener: OnBackPressListener? = null
+
+ private var isCancelable = true
+ private var contentBackgroundResource = android.R.color.white
+ private var headerViewResourceId = INVALID
+ var isFixedHeader = false
+ private set
+ private var footerViewResourceId = INVALID
+ var isFixedFooter = false
+ private set
+ private var inAnimation = INVALID
+ private var outAnimation = INVALID
+ private var expanded: Boolean = false
+ private var defaultContentHeight: Int = 0
+ private var overlayBackgroundResource = R.color.dialogcustomization_black_overlay
+
+ val contentParams: FrameLayout.LayoutParams
+ get() {
+ if (expanded) {
+ params.height = getDefaultContentHeight()
+ }
+ return params
+ }
+
+ val outmostLayoutParams: FrameLayout.LayoutParams
+ get() {
+ val params = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ params.setMargins(outMostMargin[0], outMostMargin[1], outMostMargin[2], outMostMargin[3])
+ return params
+ }
+
+ val contentMargin: IntArray
+ get() {
+ val minimumMargin = context!!.resources.getDimensionPixelSize(
+ R.dimen.dialogcustomization_default_center_margin)
+ for (i in margin.indices) {
+ margin[i] = getMargin(this.gravity, margin[i], minimumMargin)
+ }
+ return margin
+ }
+
+ private constructor() {}
+
+ /**
+ * Initialize the builder with a valid context in order to inflate the dialog
+ */
+ internal constructor(context: Context) {
+ this.context = context
+ Arrays.fill(margin, INVALID)
+ }
+
+ /**
+ * Set the adapter that will be used when ListHolder or GridHolder are passed
+ */
+ fun setAdapter(adapter: BaseAdapter): DialogBuilder {
+ this.adapter = adapter
+ return this
+ }
+
+ /**
+ * Set the footer view using the id of the layout resource
+ *
+ * @param fixed is used to determine whether footer should be fixed or not. Fixed if true, scrollable otherwise
+ */
+ @JvmOverloads
+ fun setFooter(resourceId: Int, fixed: Boolean = false): DialogBuilder {
+ this.footerViewResourceId = resourceId
+ this.isFixedFooter = fixed
+ return this
+ }
+
+ /**
+ * Sets the given view as footer.
+ *
+ * @param fixed is used to determine whether footer should be fixed or not. Fixed if true, scrollable otherwise
+ */
+ @JvmOverloads
+ fun setFooter(view: View, fixed: Boolean = false): DialogBuilder {
+ this.footerView = view
+ this.isFixedFooter = fixed
+ return this
+ }
+
+ /**
+ * Set the header view using the id of the layout resource
+ *
+ * @param fixed is used to determine whether header should be fixed or not. Fixed if true, scrollable otherwise
+ */
+ @JvmOverloads
+ fun setHeader(resourceId: Int, fixed: Boolean = false): DialogBuilder {
+ this.headerViewResourceId = resourceId
+ this.isFixedHeader = fixed
+ return this
+ }
+
+ /**
+ * Set the header view using a view
+ *
+ * @param fixed is used to determine whether header should be fixed or not. Fixed if true, scrollable otherwise
+ */
+ @JvmOverloads
+ fun setHeader(view: View, fixed: Boolean = false): DialogBuilder {
+ this.headerView = view
+ this.isFixedHeader = fixed
+ return this
+ }
+ //DialogBuilder
+ /**
+ * Define if the dialog is cancelable and should be closed when back pressed or click outside is pressed
+ */
+ fun setCancelable(isCancelable: Boolean): DialogBuilder {
+ this.isCancelable = isCancelable
+ return this
+ }
+
+ /**
+ * Set the content of the dialog by passing one of the provided Holders
+ */
+ fun setContentHolder(holder: Holder): DialogBuilder {
+ this.holder = holder
+ return this
+ }
+
+ /**
+ * Use setBackgroundResource
+ */
+ @Deprecated("")
+ fun setBackgroundColorResId(resourceId: Int): DialogBuilder {
+ return setContentBackgroundResource(resourceId)
+ }
+
+ /**
+ * Set background color for your dialog. If no resource is passed 'white' will be used
+ */
+ fun setContentBackgroundResource(resourceId: Int): DialogBuilder {
+ this.contentBackgroundResource = resourceId
+ return this
+ }
+
+ fun setOverlayBackgroundResource(resourceId: Int): DialogBuilder {
+ this.overlayBackgroundResource = resourceId
+ return this
+ }
+
+ /**
+ * Set the gravity you want the dialog to have among the ones that are provided
+ */
+ fun setGravity(gravity: Int): DialogBuilder {
+ this.gravity = gravity
+ params.gravity = gravity
+ return this
+ }
+
+ /**
+ * Customize the in animation by passing an animation resource
+ */
+ fun setInAnimation(inAnimResource: Int): DialogBuilder {
+ this.inAnimation = inAnimResource
+ return this
+ }
+
+ /**
+ * Customize the out animation by passing an animation resource
+ */
+ fun setOutAnimation(outAnimResource: Int): DialogBuilder {
+ this.outAnimation = outAnimResource
+ return this
+ }
+
+ /**
+ * Add margins to your outmost view which contains everything. As default they are 0
+ * are applied
+ */
+ fun setOutMostMargin(left: Int, top: Int, right: Int, bottom: Int): DialogBuilder {
+ this.outMostMargin[0] = left
+ this.outMostMargin[1] = top
+ this.outMostMargin[2] = right
+ this.outMostMargin[3] = bottom
+ return this
+ }
+
+ /**
+ * Add margins to your dialog. They are set to 0 except when gravity is center. In that case basic margins
+ * are applied
+ */
+ fun setMargin(left: Int, top: Int, right: Int, bottom: Int): DialogBuilder {
+ this.margin[0] = left
+ this.margin[1] = top
+ this.margin[2] = right
+ this.margin[3] = bottom
+ return this
+ }
+
+ /**
+ * Set paddings for the dialog content
+ */
+ fun setPadding(left: Int, top: Int, right: Int, bottom: Int): DialogBuilder {
+ this.contentPadding[0] = left
+ this.contentPadding[1] = top
+ this.contentPadding[2] = right
+ this.contentPadding[3] = bottom
+ return this
+ }
+
+ /**
+ * Set an item click listener when list or grid holder is chosen. In that way you can have callbacks when one
+ * of your items is clicked
+ */
+ fun setOnItemClickListener(listener: OnItemClickListener): DialogBuilder {
+ this.onItemClickListener = listener
+ return this
+ }
+
+ /**
+ * Set a global click listener to you dialog in order to handle all the possible click events. You can then
+ * identify the view by using its id and handle the correct behaviour
+ */
+ fun setOnClickListener(listener: OnClickListener): DialogBuilder {
+ this.onClickListener = listener
+ return this
+ }
+
+ fun setOnDismissListener(listener: OnDismissListener?): DialogBuilder {
+ this.onDismissListener = listener
+ return this
+ }
+
+ fun setOnCancelListener(listener: OnCancelListener?): DialogBuilder {
+ this.onCancelListener = listener
+ return this
+ }
+
+ fun setOnBackPressListener(listener: OnBackPressListener?): DialogBuilder {
+ this.onBackPressListener = listener
+ return this
+ }
+
+ fun setExpanded(expanded: Boolean): DialogBuilder {
+ this.expanded = expanded
+ return this
+ }
+
+ fun setExpanded(expanded: Boolean, defaultContentHeight: Int): DialogBuilder {
+ this.expanded = expanded
+ this.defaultContentHeight = defaultContentHeight
+ return this
+ }
+
+ fun setContentHeight(height: Int): DialogBuilder {
+ params.height = height
+ return this
+ }
+
+ fun setContentWidth(width: Int): DialogBuilder {
+ params.width = width
+ return this
+ }
+
+ /**
+ * Create the dialog using this builder
+ */
+ fun create(): DialogCustomization {
+ getHolder().setBackgroundResource(getContentBackgroundResource())
+ return DialogCustomization(this)
+ }
+
+ fun getFooterView(): View? {
+ return Utils.getView(this.context!!, footerViewResourceId, footerView)
+ }
+
+ fun getHeaderView(): View? {
+ return Utils.getView(this.context!!, headerViewResourceId, headerView)
+ }
+
+ fun getHolder(): Holder {
+ if (holder == null) {
+ holder = ListHolder()
+ }
+ return holder as Holder
+ }
+
+ fun getAdapter(): BaseAdapter? {
+ return adapter
+ }
+
+ fun getInAnimation(): Animation {
+ val res = if (inAnimation == INVALID) Utils.getAnimationResource(this.gravity, true) else inAnimation
+ return AnimationUtils.loadAnimation(context, res)
+ }
+
+ fun getOutAnimation(): Animation {
+ val res = if (outAnimation == INVALID) Utils.getAnimationResource(this.gravity, false) else outAnimation
+ return AnimationUtils.loadAnimation(context, res)
+ }
+
+ fun isExpanded(): Boolean {
+ return expanded
+ }
+
+ fun isCancelable(): Boolean {
+ return isCancelable
+ }
+
+ fun getOnItemClickListener(): OnItemClickListener? {
+ return onItemClickListener
+ }
+
+ fun getOnClickListener(): OnClickListener? {
+ return onClickListener
+ }
+
+ fun getOnDismissListener(): OnDismissListener? {
+ return onDismissListener
+ }
+
+ fun getOnCancelListener(): OnCancelListener? {
+ return onCancelListener
+ }
+
+ fun getOnBackPressListener(): OnBackPressListener? {
+ return onBackPressListener
+ }
+
+ fun getDefaultContentHeight(): Int {
+ val activity = context as Activity
+ val display = activity.windowManager.defaultDisplay
+ val displayHeight = display.height - Utils.getStatusBarHeight(activity)
+ if (defaultContentHeight == 0) {
+ defaultContentHeight = displayHeight * 2 / 5
+ }
+ return defaultContentHeight
+ }
+
+ fun getOverlayBackgroundResource(): Int {
+ return overlayBackgroundResource
+ }
+
+ fun getContentBackgroundResource(): Int {
+ return contentBackgroundResource
+ }
+
+ /**
+ * Get margins if provided or assign default values based on gravity
+ *
+ * @param gravity the gravity of the dialog
+ * @param margin the value defined in the builder
+ * @param minimumMargin the minimum margin when gravity center is selected
+ * @return the value of the margin
+ */
+ private fun getMargin(gravity: Int, margin: Int, minimumMargin: Int): Int {
+ when (gravity) {
+ Gravity.CENTER -> return if (margin == INVALID) minimumMargin else margin
+ else -> return if (margin == INVALID) 0 else margin
+ }
+ }
+
+ companion object {
+ private val INVALID = -1
+ }
+}
+/**
+ * Set the footer view using the id of the layout resource
+ */
+/**
+ * Sets the given view as footer.
+ */
+/**
+ * Set the header view using the id of the layout resource
+ */
+/**
+ * Set the header view using a view
+ */
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogCustomization.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogCustomization.kt
new file mode 100644
index 0000000..c3b6cdf
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/DialogCustomization.kt
@@ -0,0 +1,382 @@
+package com.mindorks.waprofileimage.customdialog
+
+import android.app.Activity
+import android.content.Context
+import android.view.Display
+import android.view.KeyEvent
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.view.animation.Animation
+import android.widget.AbsListView
+import android.widget.AdapterView
+import android.widget.BaseAdapter
+import android.widget.FrameLayout
+
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.customdialog.callback.*
+import com.mindorks.waprofileimage.customdialog.dialogutil.Utils
+
+class DialogCustomization internal constructor(builder: DialogBuilder) {
+
+ /**
+ * DialogCustomization base layout root view
+ */
+ private val rootView: ViewGroup
+
+ /**
+ * DialogCustomization content container which is a different layout rather than base layout
+ */
+ private val contentContainer: ViewGroup
+
+ /**
+ * Determines whether dialog should be dismissed by back button or touch in the black overlay
+ */
+ private val isCancelable: Boolean
+
+ /**
+ * Determines whether dialog is showing dismissing animation and avoid to repeat it
+ */
+ private var isDismissing: Boolean = false
+
+ /**
+ * Listener for the user to take action by clicking any item
+ */
+ private val onItemClickListener: OnItemClickListener?
+
+ /**
+ * Listener for the user to take action by clicking views in header or footer
+ */
+ private val onClickListener: OnClickListener?
+
+ /**
+ * Listener to notify the user that dialog has been dismissed
+ */
+ private val onDismissListener: OnDismissListener?
+
+ /**
+ * Listener to notify the user that dialog has been canceled
+ */
+ lateinit var onCancelListener: OnCancelListener
+
+ /**
+ * Listener to notify back press
+ */
+ private val onBackPressListener: OnBackPressListener?
+
+ /**
+ * Content
+ */
+ private val holder: Holder
+
+ /**
+ * basically activity root view
+ */
+ private val decorView: ViewGroup
+
+ private val outAnim: Animation
+ private val inAnim: Animation
+
+ /**
+ * Checks if the dialog is shown
+ */
+ val isShowing: Boolean
+ get() {
+ val view = decorView.findViewById(R.id.dialogcustomization_outmost_container)
+ return view != null
+ }
+
+ /**
+ * Returns header view if it was set.
+ */
+ val headerView: View?
+ get() = holder.header
+
+ /**
+ * Returns footer view if it was set.
+ */
+ val footerView: View?
+ get() = holder.footer
+
+ /**
+ * Returns holder view.
+ */
+ val holderView: View
+ get() = holder.inflatedView
+
+ /**
+ * Called when the user touch on black overlay in order to dismiss the dialog
+ */
+ private val onCancelableTouchListener = View.OnTouchListener { v, event ->
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ onCancelListener.onCancel(this@DialogCustomization)
+ dismiss()
+ }
+ false
+ }
+
+ init {
+ val layoutInflater = LayoutInflater.from(builder.context)
+
+ val activity = builder.context as Activity?
+
+ holder = builder.getHolder()
+
+ onItemClickListener = builder.getOnItemClickListener()
+ onClickListener = builder.getOnClickListener()
+ onDismissListener = builder.getOnDismissListener()
+ onCancelListener = builder.getOnCancelListener()!!
+ onBackPressListener = builder.getOnBackPressListener()
+ isCancelable = builder.isCancelable()
+
+ /*
+ * Avoid getting directly from the decor view because by doing that we are overlapping the black soft key on
+ * nexus device. I think it should be tested on different devices but in my opinion is the way to go.
+ * @link http://stackoverflow.com/questions/4486034/get-root-view-from-current-activity
+ */
+ decorView = activity!!.window.decorView.findViewById(android.R.id.content)
+ rootView = layoutInflater.inflate(R.layout.base_container, decorView, false) as ViewGroup
+ rootView.layoutParams = builder.outmostLayoutParams
+
+ val outmostView = rootView.findViewById(R.id.dialogcustomization_outmost_container)
+ outmostView.setBackgroundResource(builder.getOverlayBackgroundResource())
+
+ contentContainer = rootView.findViewById(R.id.dialogcustomization_content_container)
+ contentContainer.layoutParams = builder.contentParams
+
+ outAnim = builder.getOutAnimation()
+ inAnim = builder.getInAnimation()
+
+ initContentView(
+ layoutInflater,
+ builder.getHeaderView(),
+ builder.isFixedHeader,
+ builder.getFooterView(),
+ builder.isFixedFooter,
+ builder.getAdapter(),
+ builder.contentPadding,
+ builder.contentMargin
+ )
+
+ initCancelable()
+ if (builder.isExpanded()) {
+ initExpandAnimator(activity, builder.getDefaultContentHeight(), builder.contentParams.gravity)
+ }
+ }
+
+ /**
+ * Displays the dialog if it is not shown already.
+ */
+ fun show() {
+ if (isShowing) {
+ return
+ }
+ onAttached(rootView)
+ }
+
+ /**
+ * Dismisses the displayed dialog.
+ */
+ fun dismiss() {
+ if (isDismissing) {
+ return
+ }
+
+ outAnim.setAnimationListener(object : Animation.AnimationListener {
+ override fun onAnimationStart(animation: Animation) {
+
+ }
+
+ override fun onAnimationEnd(animation: Animation) {
+ decorView.post {
+ decorView.removeView(rootView)
+ isDismissing = false
+ onDismissListener?.onDismiss(this@DialogCustomization)
+ }
+ }
+
+ override fun onAnimationRepeat(animation: Animation) {
+
+ }
+ })
+ contentContainer.startAnimation(outAnim)
+ isDismissing = true
+ }
+
+ /**
+ * Checks the given resource id and return the corresponding view if it exists.
+ *
+ * @return null if it is not found
+ */
+ fun findViewById(resourceId: Int): View? {
+ return contentContainer.findViewById(resourceId)
+ }
+
+ private fun initExpandAnimator(activity: Activity, defaultHeight: Int, gravity: Int) {
+ var defaultHeight = defaultHeight
+ val display = activity.windowManager.defaultDisplay
+ val displayHeight = display.height - Utils.getStatusBarHeight(activity)
+ if (defaultHeight == 0) {
+ defaultHeight = displayHeight * 2 / 5
+ }
+
+ val view = holder.inflatedView as? AbsListView ?: return
+
+ view.setOnTouchListener(ExpandTouchListener.newListener(
+ activity, view, contentContainer, gravity, displayHeight, defaultHeight
+ ))
+ }
+
+ /**
+ * It is called in order to create content
+ */
+ private fun initContentView(inflater: LayoutInflater, header: View?, fixedHeader: Boolean, footer: View?,
+ fixedFooter: Boolean, adapter: BaseAdapter?, padding: IntArray, margin: IntArray) {
+ val contentView = createView(inflater, header, fixedHeader, footer, fixedFooter, adapter)
+ val params = FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ params.setMargins(margin[0], margin[1], margin[2], margin[3])
+ contentView.layoutParams = params
+ holderView.setPadding(padding[0], padding[1], padding[2], padding[3])
+ contentContainer.addView(contentView)
+ }
+
+ /**
+ * It is called to set whether the dialog is cancellable by pressing back button or
+ * touching the black overlay
+ */
+ private fun initCancelable() {
+ if (!isCancelable) {
+ return
+ }
+ val view = rootView.findViewById(R.id.dialogcustomization_outmost_container)
+ view.setOnTouchListener(onCancelableTouchListener)
+ }
+
+ /**
+ * it is called when the content view is created
+ *
+ * @param inflater used to inflate the content of the dialog
+ * @return any view which is passed
+ */
+ private fun createView(inflater: LayoutInflater, headerView: View?, fixedHeader: Boolean,
+ footerView: View?, fixedFooter: Boolean, adapter: BaseAdapter?): View {
+ val view = holder.getView(inflater, rootView)
+
+ if (holder is ViewHolder) {
+ assignClickListenerRecursively(view)
+ }
+
+ if (headerView != null) {
+ assignClickListenerRecursively(headerView)
+ holder.addHeader(headerView, fixedHeader)
+ }
+
+ if (footerView != null) {
+ assignClickListenerRecursively(footerView)
+ holder.addFooter(footerView, fixedFooter)
+ }
+
+ if (adapter != null && holder is HolderAdapter) {
+ val holderAdapter = holder
+ holderAdapter.setAdapter(adapter)
+ holderAdapter.setOnItemClickListener(object : OnHolderListener {
+ override fun onItemClick(item: Any, view: View, position: Int) {
+ if (onItemClickListener == null) {
+ return
+ }
+ onItemClickListener.onItemClick(this@DialogCustomization, item, view, position)
+ }
+ })
+ }
+ return view
+ }
+
+ /**
+ * Loop among the views in the hierarchy and assign listener to them
+ */
+ private fun assignClickListenerRecursively(parent: View?) {
+ if (parent == null) {
+ return
+ }
+
+ if (parent is ViewGroup) {
+ val viewGroup = parent as ViewGroup?
+ val childCount = viewGroup!!.childCount
+ for (i in childCount - 1 downTo 0) {
+ val child = viewGroup.getChildAt(i)
+ assignClickListenerRecursively(child)
+ }
+ }
+ setClickListener(parent)
+ }
+
+ /**
+ * It is used to setListener on view that have a valid id associated
+ */
+ private fun setClickListener(view: View) {
+ if (view.id == INVALID) {
+ return
+ }
+ //adapterview does not support click listener
+ if (view is AdapterView<*>) {
+ return
+ }
+
+ view.setOnClickListener(View.OnClickListener { v ->
+ if (onClickListener == null) {
+ return@OnClickListener
+ }
+
+ onClickListener.onClick(this@DialogCustomization, v)
+ })
+ }
+
+ /**
+ * It is called when the show() method is called
+ *
+ * @param view is the dialog plus view
+ */
+ private fun onAttached(view: View) {
+ decorView.addView(view)
+ contentContainer.startAnimation(inAnim)
+
+ contentContainer.requestFocus()
+ holder.setOnKeyListener(View.OnKeyListener { v, keyCode, event ->
+ when (event.action) {
+ KeyEvent.ACTION_UP -> if (keyCode == KeyEvent.KEYCODE_BACK) {
+ onBackPressListener?.onBackPressed(this@DialogCustomization)
+ if (isCancelable) {
+ onBackPressed(this@DialogCustomization)
+ }
+ return@OnKeyListener true
+ }
+ else -> {
+ }
+ }
+ false
+ })
+ }
+
+ /**
+ * Invoked when back button is pressed. Automatically dismiss the dialog.
+ */
+ fun onBackPressed(dialogCustomization: DialogCustomization) {
+ onCancelListener?.onCancel(this@DialogCustomization)
+ dismiss()
+ }
+
+ companion object {
+
+ private val INVALID = -1
+
+ /**
+ * Creates a new dialog builder
+ */
+ fun newDialog(context: Context): DialogBuilder {
+ return DialogBuilder(context)
+ }
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/Holder.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/Holder.kt
new file mode 100644
index 0000000..6531233
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/Holder.kt
@@ -0,0 +1,51 @@
+package com.mindorks.waprofileimage.customdialog
+
+import android.support.annotation.ColorRes
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+
+interface Holder {
+
+ val inflatedView: View
+
+ val header: View?
+
+ val footer: View?
+
+ /**
+ * Adds the given view as header to the top of holder
+ */
+ fun addHeader(view: View)
+
+ /**
+ * Adds the given view as header to the top of holder
+ *
+ * @param view will be shown as header.
+ * @param fixed fixed on top if it is true, scrollable otherwise
+ */
+ fun addHeader(view: View, fixed: Boolean)
+
+ /**
+ * Adds the given view as footer to the bottom of holder
+ */
+ fun addFooter(view: View)
+
+ /**
+ * Adds the given view as footer to the bottom of holder
+ *
+ * @param view will be shown as footer.
+ * @param fixed fixed at bottom if it is true, scrollable otherwise
+ */
+ fun addFooter(view: View, fixed: Boolean)
+
+ /**
+ * Sets the given color resource as background for the content
+ */
+ fun setBackgroundResource(@ColorRes colorResource: Int)
+
+ fun getView(inflater: LayoutInflater, parent: ViewGroup): View
+
+ fun setOnKeyListener(keyListener: View.OnKeyListener)
+
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/HolderAdapter.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/HolderAdapter.kt
new file mode 100644
index 0000000..bda9523
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/HolderAdapter.kt
@@ -0,0 +1,10 @@
+package com.mindorks.waprofileimage.customdialog
+
+import android.widget.BaseAdapter
+
+interface HolderAdapter : Holder {
+
+ fun setAdapter(adapter: BaseAdapter)
+
+ fun setOnItemClickListener(listener: OnHolderListener)
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ListHolder.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ListHolder.kt
new file mode 100644
index 0000000..97d384e
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ListHolder.kt
@@ -0,0 +1,102 @@
+package com.mindorks.waprofileimage.customdialog
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.BaseAdapter
+import android.widget.LinearLayout
+import android.widget.ListView
+import com.mindorks.waprofileimage.R
+
+class ListHolder : HolderAdapter, AdapterView.OnItemClickListener {
+ private var backgroundResource: Int = 0
+
+ private var listView: ListView? = null
+ private var listener: OnHolderListener? = null
+ private var keyListener: View.OnKeyListener? = null
+ private var headerView: View? = null
+ private var footerView: View? = null
+ private var footerContainer: ViewGroup? = null
+ private var headerContainer: ViewGroup? = null
+
+ override fun addHeader(view: View) {
+ addHeader(view, false)
+ }
+
+ override fun addHeader(view: View, fixed: Boolean) {
+ if (fixed) {
+ headerContainer!!.addView(view)
+ } else {
+ listView!!.addHeaderView(view)
+ }
+ headerView = view
+ }
+
+ override fun addFooter(view: View) {
+ addFooter(view, false)
+ }
+
+ override fun addFooter(view: View, fixed: Boolean) {
+ if (fixed) {
+ footerContainer!!.addView(view)
+ } else {
+ listView!!.addFooterView(view)
+ }
+ footerView = view
+ }
+
+ override fun setAdapter(adapter: BaseAdapter) {
+ listView!!.adapter = adapter
+ }
+
+ override fun setBackgroundResource(colorResource: Int) {
+ this.backgroundResource = colorResource
+ }
+
+ override fun getView(inflater: LayoutInflater, parent: ViewGroup): View {
+ val view = inflater.inflate(R.layout.dialog_list, parent, false)
+ val outMostView = view.findViewById(R.id.dialogcustomization_outmost_container)
+ outMostView.setBackgroundResource(backgroundResource)
+ listView = view.findViewById(R.id.dialogcustomization_list)
+ listView!!.onItemClickListener = this
+ listView!!.setOnKeyListener { v, keyCode, event ->
+ if (keyListener == null) {
+ throw NullPointerException("keyListener should not be null")
+ }
+ keyListener!!.onKey(v, keyCode, event)
+ }
+ headerContainer = view.findViewById(R.id.dialogcustomization_header_container)
+ footerContainer = view.findViewById(R.id.dialogcustomization_footer_container)
+ return view
+ }
+
+ override fun setOnItemClickListener(listener: OnHolderListener) {
+ this.listener = listener
+ }
+
+ override fun setOnKeyListener(keyListener: View.OnKeyListener) {
+ this.keyListener = keyListener
+ }
+
+ override val inflatedView: View
+ get() = this.listView!!
+
+ override val header: View?
+ get() = headerView
+ override val footer: View?
+ get() = footerView
+
+ override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
+ var position = position
+ if (listener == null) {
+ return
+ }
+ //ListView counts header as position as well. For consistency we don't
+ listener!!.onItemClick(
+ parent.getItemAtPosition(position),
+ view,
+ if (headerView != null) --position else position
+ )
+ }
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/OnHolderListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/OnHolderListener.kt
new file mode 100644
index 0000000..7012fd5
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/OnHolderListener.kt
@@ -0,0 +1,9 @@
+package com.mindorks.waprofileimage.customdialog
+
+import android.view.View
+
+interface OnHolderListener {
+
+ fun onItemClick(item: Any, view: View, position: Int)
+
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/SimpleAdapter.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/SimpleAdapter.kt
new file mode 100644
index 0000000..e8087fd
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/SimpleAdapter.kt
@@ -0,0 +1,66 @@
+package com.mindorks.waprofileimage.customdialog
+
+import android.content.Context
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.BaseAdapter
+import android.widget.ImageView
+import android.widget.TextView
+import com.mindorks.waprofileimage.R
+
+class SimpleAdapter(
+ context: Context,
+ private val count: Int) : BaseAdapter() {
+
+ private val layoutInflater: LayoutInflater = LayoutInflater.from(context)
+
+ override fun getCount(): Int {
+ return count
+ }
+
+ override fun getItem(position: Int): Any {
+ return position
+ }
+
+ override fun getItemId(position: Int): Long {
+ return position.toLong()
+ }
+
+ override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
+ val viewHolder: ViewHolder
+ var view: View? = convertView
+
+ if (view == null) {
+ view = layoutInflater.inflate(R.layout.item_dialog, parent, false)
+
+ viewHolder = ViewHolder(
+ view.findViewById(R.id.text_view),
+ view.findViewById(R.id.image_view)
+ )
+ view.tag = viewHolder
+ } else {
+ viewHolder = view.tag as ViewHolder
+ }
+
+ val context = parent.context
+ when (position) {
+ 0 -> {
+ viewHolder.textView.text = context.getString(R.string.gallery_title)
+ // viewHolder.imageView.setImageResource(R.drawable.ic_google_plus_icon)
+ }
+ 1 -> {
+ viewHolder.textView.text = context.getString(R.string.camera_title)
+ // viewHolder.imageView.setImageResource(R.drawable.ic_google_maps_icon)
+ }
+ else -> {
+ viewHolder.textView.text = context.getString(R.string.remove_photo_title)
+ // viewHolder.imageView.setImageResource(R.drawable.ic_google_messenger_icon)
+ }
+ }
+
+ return view!!
+ }
+
+ data class ViewHolder(val textView: TextView, val imageView: ImageView)
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ViewHolder.java b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ViewHolder.java
new file mode 100644
index 0000000..af9d1f4
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/ViewHolder.java
@@ -0,0 +1,112 @@
+package com.mindorks.waprofileimage.customdialog;
+
+import android.support.annotation.NonNull;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.mindorks.waprofileimage.R;
+
+public class ViewHolder implements Holder {
+ private static final int INVALID = -1;
+ private int backgroundResource;
+ private ViewGroup headerContainer;
+ private View headerView;
+ private ViewGroup footerContainer;
+ private View footerView;
+ private View.OnKeyListener keyListener;
+ private View contentView;
+ private int viewResourceId = INVALID;
+
+ public ViewHolder(int viewResourceId) {
+ this.viewResourceId = viewResourceId;
+ }
+
+ public ViewHolder(View contentView) {
+ this.contentView = contentView;
+ }
+
+ @Override
+ public void addHeader(@NonNull View view) {
+ addHeader(view, false);
+ }
+
+ @Override
+ public void addHeader(@NonNull View view, boolean fixed) {
+ headerContainer.addView(view);
+ headerView = view;
+ }
+
+ @Override
+ public void addFooter(@NonNull View view) {
+ addFooter(view, false);
+ }
+
+ @Override
+ public void addFooter(@NonNull View view, boolean fixed) {
+ footerContainer.addView(view);
+ footerView = view;
+ }
+
+ @Override
+ public void setBackgroundResource(int colorResource) {
+ this.backgroundResource = colorResource;
+ }
+
+ @Override
+ @NonNull
+ public View getView(@NonNull LayoutInflater inflater, ViewGroup parent) {
+ View view = inflater.inflate(R.layout.layout_dialog, parent, false);
+ View outMostView = view.findViewById(R.id.dialogcustomization_outmost_container);
+ outMostView.setBackgroundResource(backgroundResource);
+ ViewGroup contentContainer = view.findViewById(R.id.dialogcustomization_view_container);
+ contentContainer.setOnKeyListener(new View.OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (keyListener == null) {
+ throw new NullPointerException("keyListener should not be null");
+ }
+ return keyListener.onKey(v, keyCode, event);
+ }
+ });
+ addContent(inflater, parent, contentContainer);
+ headerContainer = view.findViewById(R.id.dialogcustomization_header_container);
+ footerContainer = view.findViewById(R.id.dialogcustomization_footer_container);
+ return view;
+ }
+
+ private void addContent(LayoutInflater inflater, ViewGroup parent, ViewGroup container) {
+ if (viewResourceId != INVALID) {
+ contentView = inflater.inflate(viewResourceId, parent, false);
+ } else {
+ ViewGroup parentView = (ViewGroup) contentView.getParent();
+ if (parentView != null) {
+ parentView.removeView(contentView);
+ }
+ }
+
+ container.addView(contentView);
+ }
+
+ @Override
+ public void setOnKeyListener(View.OnKeyListener keyListener) {
+ this.keyListener = keyListener;
+ }
+
+ @Override
+ @NonNull
+ public View getInflatedView() {
+ return contentView;
+ }
+
+ @Override
+ public View getHeader() {
+ return headerView;
+ }
+
+ @Override
+ public View getFooter() {
+ return footerView;
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/animation/HeightAnimation.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/animation/HeightAnimation.kt
new file mode 100644
index 0000000..d77701e
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/animation/HeightAnimation.kt
@@ -0,0 +1,22 @@
+package com.mindorks.waprofileimage.customdialog.animation
+
+import android.view.View
+import android.view.animation.Animation
+import android.view.animation.Transformation
+
+internal class HeightAnimation(private val view: View, private val originalHeight: Int, toHeight: Int) : Animation() {
+ private val perValue: Float
+
+ init {
+ this.perValue = (toHeight - originalHeight).toFloat()
+ }
+
+ override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
+ view.layoutParams.height = (originalHeight + perValue * interpolatedTime).toInt()
+ view.requestLayout()
+ }
+
+ override fun willChangeBounds(): Boolean {
+ return true
+ }
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/ExpandTouchListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/ExpandTouchListener.kt
new file mode 100644
index 0000000..ae5d2e6
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/ExpandTouchListener.kt
@@ -0,0 +1,181 @@
+package com.mindorks.waprofileimage.customdialog.callback
+
+import android.content.Context
+import android.view.GestureDetector
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.View
+import android.view.animation.Animation
+import android.widget.AbsListView
+import android.widget.FrameLayout
+
+import com.mindorks.waprofileimage.customdialog.dialogutil.Utils
+
+internal class ExpandTouchListener private constructor(context: Context, private val absListView: AbsListView, private val contentContainer: View, private val gravity: Int,
+ /**
+ * This is used to determine top. status bar height is removed
+ */
+ private val displayHeight: Int,
+ /**
+ * Default height for the holder
+ */
+ private val defaultContentHeight: Int) : View.OnTouchListener {
+ private val gestureDetector: GestureDetector
+
+ /**
+ * The last touch position in Y Axis
+ */
+ private var y: Float = 0.toFloat()
+
+ /**
+ * This is used to determine whether dialog reached to top or not.
+ */
+ private var fullScreen: Boolean = false
+
+ /**
+ * This is used to determine whether the user swipes from down to top.
+ * touchUp is calculated by touch events
+ */
+ private var touchUp: Boolean = false
+
+ /**
+ * This is used to determine whether the user swipes from down to top.
+ * scrollUp is calculated from gesture detector scroll event.
+ * This shouldn't be used for the touch events
+ */
+ private var scrollUp: Boolean = false
+
+ /**
+ * Content container params, not the holder itself.
+ */
+ private val params: FrameLayout.LayoutParams
+
+ init {
+ this.params = contentContainer.layoutParams as FrameLayout.LayoutParams
+
+ gestureDetector = GestureDetector(context, object : GestureDetector.SimpleOnGestureListener() {
+ override fun onSingleTapUp(e: MotionEvent): Boolean {
+ return true
+ }
+
+ override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
+ scrollUp = distanceY > 0
+ return false
+ }
+ })
+ }
+
+ override fun onTouch(v: View, event: MotionEvent): Boolean {
+ //If single tapped, don't consume the event
+ if (gestureDetector.onTouchEvent(event)) {
+ return false
+ }
+
+ // when the dialog is fullscreen and user scrolls the content
+ // don't consume the event
+ if (!(!scrollUp && Utils.listIsAtTop(absListView)) && fullScreen) {
+ return false
+ }
+
+ when (event.action) {
+ MotionEvent.ACTION_DOWN -> {
+ y = event.rawY
+ return true
+ }
+ MotionEvent.ACTION_MOVE -> {
+ // This is a quick fix to not trigger click event
+ if (params.height == displayHeight) {
+ params.height--
+ contentContainer.layoutParams = params
+ return false
+ }
+ onTouchMove(v, event)
+ }
+ MotionEvent.ACTION_UP -> onTouchUp(v, event)
+ else -> {
+ }
+ }
+ return true
+ }
+
+ private fun onTouchMove(view: View, event: MotionEvent) {
+ // sometimes Action_DOWN is not called, we need to make sure that
+ // we calculate correct y value
+ if (y == -1f) {
+ y = event.rawY
+ }
+ var delta = y - event.rawY
+ // if delta > 0 , that means user swipes to top
+ touchUp = delta > 0
+ if (gravity == Gravity.TOP) {
+ delta = -delta
+ }
+ //update the y value, otherwise delta will be incorrect
+ y = event.rawY
+
+ var newHeight = params.height + delta.toInt()
+
+ // This prevents dialog to move out of screen bounds
+ if (newHeight > displayHeight) {
+ newHeight = displayHeight
+ }
+
+ // This prevents the dialog go below the default value while dragging
+ if (newHeight < defaultContentHeight) {
+ newHeight = defaultContentHeight
+ }
+ params.height = newHeight
+ contentContainer.layoutParams = params
+
+ // we use fullscreen value to activate view content scroll
+ fullScreen = params.height == displayHeight
+ }
+
+ private fun onTouchUp(view: View, event: MotionEvent) {
+ // reset y value
+ y = -1f
+
+ // if the dragging direction is from top to down and dialog position still can't exceeds threshold
+ // move the dialog automatically to top
+ if (!touchUp && params.height < displayHeight && params.height > displayHeight * 4 / 5) {
+ Utils.animateContent(contentContainer, displayHeight, object : SimpleAnimationListener() {
+ override fun onAnimationEnd(animation: Animation) {
+ fullScreen = true
+ }
+ })
+ return
+ }
+
+ // if the dragging direction is down to top and dialog is dragged more than 50 to up
+ // move the dialog automatically to top
+ if (touchUp && params.height > defaultContentHeight + 50) {
+ Utils.animateContent(contentContainer, displayHeight, object : SimpleAnimationListener() {
+ override fun onAnimationEnd(animation: Animation) {
+ fullScreen = true
+ }
+ })
+ return
+ }
+
+ // if the dragging direction is from down to top and dialog position still can't exceeds threshold
+ // move the dialog automatically to down
+ if (touchUp && params.height <= defaultContentHeight + 50) {
+ Utils.animateContent(contentContainer, defaultContentHeight, SimpleAnimationListener())
+ return
+ }
+
+ // if the dragging direction is from top to down and the position exceeded the threshold
+ // move the dialog to down
+ if (!touchUp && params.height > defaultContentHeight) {
+ Utils.animateContent(contentContainer, defaultContentHeight, SimpleAnimationListener())
+ }
+ }
+
+ companion object {
+
+ fun newListener(context: Context, listView: AbsListView, container: View,
+ gravity: Int, displayHeight: Int, defaultContentHeight: Int): ExpandTouchListener {
+ return ExpandTouchListener(context, listView, container, gravity, displayHeight, defaultContentHeight)
+ }
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnBackPressListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnBackPressListener.kt
new file mode 100644
index 0000000..6dce35b
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnBackPressListener.kt
@@ -0,0 +1,15 @@
+package com.mindorks.waprofileimage.customdialog.callback
+
+import com.mindorks.waprofileimage.customdialog.DialogCustomization
+
+/**
+ * dialogcustomization tries to listen back press actions.
+ */
+interface OnBackPressListener {
+
+ /**
+ * Invoked when dialogcustomization receives any back press button event.
+ */
+ fun onBackPressed(dialog: DialogCustomization)
+
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnCancelListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnCancelListener.kt
new file mode 100644
index 0000000..eb17fe2
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnCancelListener.kt
@@ -0,0 +1,11 @@
+package com.mindorks.waprofileimage.customdialog.callback
+
+import com.mindorks.waprofileimage.customdialog.DialogCustomization
+
+/**
+ * dialogcustomization will use this listener to propagate cancel events when back button is pressed.
+ */
+interface OnCancelListener {
+
+ fun onCancel(dialog: DialogCustomization)
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnClickListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnClickListener.kt
new file mode 100644
index 0000000..f96a838
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnClickListener.kt
@@ -0,0 +1,16 @@
+package com.mindorks.waprofileimage.customdialog.callback
+
+import android.view.View
+
+import com.mindorks.waprofileimage.customdialog.DialogCustomization
+
+interface OnClickListener {
+
+ /**
+ * Invoked when any view within ViewHolder is clicked.
+ *
+ * @param view is the clicked view
+ */
+ fun onClick(dialog: DialogCustomization, view: View)
+
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnDismissListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnDismissListener.kt
new file mode 100644
index 0000000..de834a0
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnDismissListener.kt
@@ -0,0 +1,13 @@
+package com.mindorks.waprofileimage.customdialog.callback
+
+import com.mindorks.waprofileimage.customdialog.DialogCustomization
+
+/**
+ * Invoked when dialog is completely dismissed. This listener takes the animation into account and waits for it.
+ *
+ *
+ * It is invoked after animation is completed
+ */
+interface OnDismissListener {
+ fun onDismiss(dialog: DialogCustomization)
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnItemClickListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnItemClickListener.kt
new file mode 100644
index 0000000..d8aa19f
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/OnItemClickListener.kt
@@ -0,0 +1,11 @@
+package com.mindorks.waprofileimage.customdialog.callback
+
+import android.view.View
+
+import com.mindorks.waprofileimage.customdialog.DialogCustomization
+
+interface OnItemClickListener {
+
+ fun onItemClick(dialog: DialogCustomization, item: Any, view: View, position: Int)
+
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/SimpleAnimationListener.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/SimpleAnimationListener.kt
new file mode 100644
index 0000000..9a1e6f8
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/callback/SimpleAnimationListener.kt
@@ -0,0 +1,17 @@
+package com.mindorks.waprofileimage.customdialog.callback
+
+import android.view.animation.Animation
+
+open class SimpleAnimationListener : Animation.AnimationListener {
+ override fun onAnimationStart(animation: Animation) {
+
+ }
+
+ override fun onAnimationEnd(animation: Animation) {
+
+ }
+
+ override fun onAnimationRepeat(animation: Animation) {
+
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/dialogutil/Utils.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/dialogutil/Utils.kt
new file mode 100644
index 0000000..7e6db86
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/customdialog/dialogutil/Utils.kt
@@ -0,0 +1,67 @@
+package com.mindorks.waprofileimage.customdialog.dialogutil
+
+import android.content.Context
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.animation.Animation
+import android.widget.AbsListView
+
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.customdialog.animation.HeightAnimation
+
+ object Utils {
+
+ private val INVALID = -1
+
+ fun getStatusBarHeight(context: Context): Int {
+ var result = 0
+ val resourceId = context.resources.getIdentifier("status_bar_height", "dimen", "android")
+ if (resourceId > 0) {
+ result = context.resources.getDimensionPixelSize(resourceId)
+ }
+ return result
+ }
+
+ fun animateContent(view: View, to: Int, listener: Animation.AnimationListener) {
+ val animation = HeightAnimation(view, view.height, to)
+ animation.setAnimationListener(listener)
+ animation.duration = 200
+ view.startAnimation(animation)
+ }
+
+ fun listIsAtTop(listView: AbsListView): Boolean {
+ return listView.childCount == 0 || listView.getChildAt(0).top == listView.paddingTop
+ }
+
+ /**
+ * This will be called in order to create view, if the given view is not null,
+ * it will be used directly, otherwise it will check the resourceId
+ *
+ * @return null if both resourceId and view is not set
+ */
+ fun getView(context: Context, resourceId: Int, view: View?): View? {
+ var view = view
+ val inflater = LayoutInflater.from(context)
+ if (view != null) {
+ return view
+ }
+ if (resourceId != INVALID) {
+ view = inflater.inflate(resourceId, null)
+ }
+ return view
+ }
+
+ /**
+ * Get default animation resource when not defined by the user
+ *
+ * @param gravity the gravity of the dialog
+ * @param isInAnimation determine if is in or out animation. true when is is
+ * @return the id of the animation resource
+ */
+ fun getAnimationResource(gravity: Int, isInAnimation: Boolean): Int {
+ return if (gravity and Gravity.BOTTOM == Gravity.BOTTOM) {
+ if (isInAnimation) R.anim.slide_in_bottom else R.anim.slide_out_bottom
+ } else INVALID
+ }
+}// no instance
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/util/PermUtil.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/util/PermUtil.kt
new file mode 100644
index 0000000..2e79610
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/util/PermUtil.kt
@@ -0,0 +1,41 @@
+package com.mindorks.waprofileimage.util
+
+import android.Manifest
+import android.app.Activity
+import android.content.pm.PackageManager
+import android.os.Build
+import android.support.annotation.RequiresApi
+import android.support.v4.app.FragmentActivity
+import com.mindorks.waprofileimage.callback.TaskFinished
+import java.util.ArrayList
+
+object PermUtil {
+ val REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 9921
+
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ fun checkForCamara_WritePermissions(activity: FragmentActivity, taskFinished: TaskFinished) {
+ val permissionsNeeded = ArrayList()
+ val permissionsList = ArrayList()
+ if (!addPermission(permissionsList, Manifest.permission.CAMERA, activity))
+ permissionsNeeded.add("CAMERA")
+ if (!addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE, activity))
+ permissionsNeeded.add("WRITE_EXTERNAL_STORAGE")
+ if (permissionsList.size > 0) {
+ activity.requestPermissions(permissionsList.toTypedArray(),
+ REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS)
+ } else {
+ taskFinished.onTaskFinished(true)
+ }
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.M)
+ private fun addPermission(permissionsList: MutableList, permission: String, ac: Activity): Boolean {
+ if (ac.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+ permissionsList.add(permission)
+ // Check for Rationale Option
+ return ac.shouldShowRequestPermissionRationale(permission)
+ }
+ return true
+ }
+
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImage.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImage.kt
new file mode 100644
index 0000000..6ecb563
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/WAProfileImage.kt
@@ -0,0 +1,48 @@
+package com.mindorks.waprofileimage.waprofileImage
+
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.os.Build
+import android.support.v4.app.FragmentActivity
+import com.mindorks.waprofileimage.callback.TaskFinished
+import com.mindorks.waprofileimage.util.PermUtil
+import com.mindorks.waprofileimage.waprofileImage.wapprofileimageactivity.WAProfileImageActivity
+
+
+object WAProfileImage {
+
+ val REQUEST_CODE_KEY = "REQUEST_CODE"
+ val RESPONSE_CODE_REMOVE_IMAGE = 3
+ val RESPONSE_CODE_OPEN_CAMERA = 2
+ val RESPONSE_CODE_OPEN_GALLERY = 1
+ val REQUEST_CODE_DEFAULT_VALUE = 0
+ val IMAGE_PICKED_KEY = "IMAGE_KEY"
+
+ fun launch(context: FragmentActivity, requestCode: Int) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ PermUtil.checkForCamara_WritePermissions(context, object : TaskFinished {
+ override fun onTaskFinished(check: Boolean?) {
+ val i = Intent(context, WAProfileImageActivity::class.java)
+ i.putExtra(REQUEST_CODE_KEY, requestCode);
+ context.startActivityForResult(i, requestCode)
+ }
+ })
+ } else {
+ val i = Intent(context, WAProfileImageActivity::class.java)
+ i.putExtra(REQUEST_CODE_KEY, requestCode);
+ context.startActivityForResult(i, requestCode)
+
+
+ }
+
+
+ }
+
+ fun getCameraBitMapImage(data: Intent): Bitmap {
+ val byteArray = data.getByteArrayExtra(IMAGE_PICKED_KEY)
+ val bmp = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
+ return bmp
+ }
+
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ActivityExtensions.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ActivityExtensions.kt
new file mode 100644
index 0000000..1751896
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ActivityExtensions.kt
@@ -0,0 +1,18 @@
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+import android.support.v4.app.FragmentActivity
+import android.support.v4.content.ContextCompat
+import android.widget.Toast
+
+/**
+ * This file illustrates Kotlin's Extension Functions by extending FragmentActivity.
+ */
+
+/**
+ * Shows a [Toast] on the UI thread.
+ *
+ * @param text The message to show
+ */
+fun FragmentActivity.showToast(text: String) {
+ runOnUiThread { Toast.makeText(this, text, Toast.LENGTH_SHORT).show() }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/AutoFitTextureView.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/AutoFitTextureView.kt
new file mode 100644
index 0000000..eccaf63
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/AutoFitTextureView.kt
@@ -0,0 +1,52 @@
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.TextureView
+import android.view.View
+
+/**
+ * A [TextureView] that can be adjusted to a specified aspect ratio.
+ */
+class AutoFitTextureView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyle: Int = 0
+) : TextureView(context, attrs, defStyle) {
+
+ private var ratioWidth = 0
+ private var ratioHeight = 0
+
+ /**
+ * Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
+ * calculated from the parameters. Note that the actual sizes of parameters don't matter, that
+ * is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
+ *
+ * @param width Relative horizontal size
+ * @param height Relative vertical size
+ */
+ fun setAspectRatio(width: Int, height: Int) {
+ if (width < 0 || height < 0) {
+ throw IllegalArgumentException("Size cannot be negative.")
+ }
+ ratioWidth = width
+ ratioHeight = height
+ requestLayout()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val width = View.MeasureSpec.getSize(widthMeasureSpec)
+ val height = View.MeasureSpec.getSize(heightMeasureSpec)
+ if (ratioWidth == 0 || ratioHeight == 0) {
+ setMeasuredDimension(width, height)
+ } else {
+ if (width < height * ratioWidth / ratioHeight) {
+ setMeasuredDimension(width, width * ratioHeight / ratioWidth)
+ } else {
+ setMeasuredDimension(height * ratioWidth / ratioHeight, height)
+ }
+ }
+ }
+
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/Camera2BasicFragment.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/Camera2BasicFragment.kt
new file mode 100644
index 0000000..4a44c9f
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/Camera2BasicFragment.kt
@@ -0,0 +1,922 @@
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+import android.Manifest
+import android.content.ContentValues
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.res.Configuration
+import android.graphics.*
+import android.hardware.camera2.*
+import android.media.ImageReader
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.HandlerThread
+import android.provider.MediaStore
+import android.support.v4.app.ActivityCompat
+import android.support.v4.app.Fragment
+import android.support.v4.content.ContextCompat
+import android.util.Log
+import android.util.Size
+import android.util.SparseIntArray
+import android.view.*
+import android.webkit.ValueCallback
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.waprofileImage.WAProfileImage.IMAGE_PICKED_KEY
+import com.mindorks.waprofileimage.waprofileImage.WAProfileImage.RESPONSE_CODE_OPEN_CAMERA
+import java.io.ByteArrayOutputStream
+import java.io.File
+import java.util.Arrays
+import java.util.Collections
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
+import kotlin.collections.ArrayList
+
+class Camera2BasicFragment : Fragment(), View.OnClickListener,
+ ActivityCompat.OnRequestPermissionsResultCallback {
+
+ /**
+ * [TextureView.SurfaceTextureListener] handles several lifecycle events on a
+ * [TextureView].
+ */
+ private val surfaceTextureListener = object : TextureView.SurfaceTextureListener {
+
+ override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) {
+ openCamera(width, height)
+ }
+
+ override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) {
+ configureTransform(width, height)
+ }
+
+ override fun onSurfaceTextureDestroyed(texture: SurfaceTexture) = true
+
+ override fun onSurfaceTextureUpdated(texture: SurfaceTexture) = Unit
+
+ }
+
+ /**
+ * ID of the current [CameraDevice].
+ */
+ private lateinit var cameraId: String
+
+ /**
+ * An [AutoFitTextureView] for camera preview.
+ */
+ private lateinit var textureView: AutoFitTextureView
+
+ /**
+ * A [CameraCaptureSession] for camera preview.
+ */
+ private var captureSession: CameraCaptureSession? = null
+
+ /**
+ * A reference to the opened [CameraDevice].
+ */
+ private var cameraDevice: CameraDevice? = null
+
+ /**
+ * The [android.util.Size] of camera preview.
+ */
+ private lateinit var previewSize: Size
+
+ /**
+ * [CameraDevice.StateCallback] is called when [CameraDevice] changes its state.
+ */
+ private val stateCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ object : CameraDevice.StateCallback() {
+
+ override fun onOpened(cameraDevice: CameraDevice) {
+ cameraOpenCloseLock.release()
+ this@Camera2BasicFragment.cameraDevice = cameraDevice
+ createCameraPreviewSession()
+ }
+
+ override fun onDisconnected(cameraDevice: CameraDevice) {
+ cameraOpenCloseLock.release()
+ cameraDevice.close()
+ this@Camera2BasicFragment.cameraDevice = null
+ }
+
+ override fun onError(cameraDevice: CameraDevice, error: Int) {
+ onDisconnected(cameraDevice)
+ this@Camera2BasicFragment.activity?.finish()
+ }
+
+ }
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+
+ /**
+ * An additional thread for running tasks that shouldn't block the UI.
+ */
+ private var backgroundThread: HandlerThread? = null
+
+ /**
+ * A [Handler] for running tasks in the background.
+ */
+ private var backgroundHandler: Handler? = null
+
+ /**
+ * An [ImageReader] that handles still image capture.
+ */
+ private var imageReader: ImageReader? = null
+
+ /**
+ * This is the output file for our picture.
+ */
+ private lateinit var file: File
+
+ /**
+ * This a callback object for the [ImageReader]. "onImageAvailable" will be called when a
+ * still image is ready to be saved.
+ */
+ private val onImageAvailableListener = ImageReader.OnImageAvailableListener {
+ val img = it.acquireNextImage()
+ backgroundHandler?.post(ImageSaver(img, file, isSuccessCallback = ValueCallback {
+ // activity!!.finish()
+
+ }))
+ }
+
+ /**
+ * [CaptureRequest.Builder] for the camera preview
+ */
+ private lateinit var previewRequestBuilder: CaptureRequest.Builder
+
+ /**
+ * [CaptureRequest] generated by [.previewRequestBuilder]
+ */
+ private lateinit var previewRequest: CaptureRequest
+
+ /**
+ * The current state of camera state for taking pictures.
+ *
+ * @see .captureCallback
+ */
+ private var state = STATE_PREVIEW
+
+ /**
+ * A [Semaphore] to prevent the app from exiting before closing the camera.
+ */
+ private val cameraOpenCloseLock = Semaphore(1)
+
+ /**
+ * Whether the current camera device supports Flash or not.
+ */
+ private var flashSupported = false
+
+ /**
+ * Orientation of the camera sensor
+ */
+ private var sensorOrientation = 0
+
+ /**
+ * A [CameraCaptureSession.CaptureCallback] that handles events related to JPEG capture.
+ */
+ private val captureCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ object : CameraCaptureSession.CaptureCallback() {
+
+ private fun process(result: CaptureResult) {
+ when (state) {
+ STATE_PREVIEW -> Unit // Do nothing when the camera preview is working normally.
+ STATE_WAITING_LOCK -> capturePicture(result)
+ STATE_WAITING_PRECAPTURE -> {
+ // CONTROL_AE_STATE can be null on some devices
+ val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
+ if (aeState == null ||
+ aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE ||
+ aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
+ state = STATE_WAITING_NON_PRECAPTURE
+ }
+ }
+ STATE_WAITING_NON_PRECAPTURE -> {
+ // CONTROL_AE_STATE can be null on some devices
+ val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
+ if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
+ state = STATE_PICTURE_TAKEN
+ captureStillPicture()
+ }
+ }
+ }
+ }
+
+ private fun capturePicture(result: CaptureResult) {
+ val afState = result.get(CaptureResult.CONTROL_AF_STATE)
+ if (afState == null) {
+ captureStillPicture()
+ } else if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED
+ || afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
+ // CONTROL_AE_STATE can be null on some devices
+ val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
+ if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
+ state = STATE_PICTURE_TAKEN
+ captureStillPicture()
+ } else {
+ runPrecaptureSequence()
+ }
+ }
+ }
+
+ override fun onCaptureProgressed(session: CameraCaptureSession,
+ request: CaptureRequest,
+ partialResult: CaptureResult) {
+ process(partialResult)
+ }
+
+ override fun onCaptureCompleted(session: CameraCaptureSession,
+ request: CaptureRequest,
+ result: TotalCaptureResult) {
+ process(result)
+ }
+
+ }
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+
+ override fun onCreateView(inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? = inflater.inflate(R.layout.fragment_camera2_basic, container, false)
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ view.findViewById(R.id.picture).setOnClickListener(this)
+ textureView = view.findViewById(R.id.texture)
+ }
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ file = File(activity!!.getExternalFilesDir(null), PIC_FILE_NAME)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ startBackgroundThread()
+
+ // When the screen is turned off and turned back on, the SurfaceTexture is already
+ // available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
+ // a camera and start preview from here (otherwise, we wait until the surface is ready in
+ // the SurfaceTextureListener).
+ if (textureView.isAvailable) {
+ openCamera(textureView.width, textureView.height)
+ } else {
+ textureView.surfaceTextureListener = surfaceTextureListener
+ }
+ }
+
+ override fun onPause() {
+ closeCamera()
+ stopBackgroundThread()
+ super.onPause()
+ }
+
+ private fun requestCameraPermission() {
+ if (shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
+ ConfirmationDialog().show(childFragmentManager, FRAGMENT_DIALOG)
+ } else {
+ requestPermissions(arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
+ }
+ }
+
+ override fun onRequestPermissionsResult(requestCode: Int,
+ permissions: Array,
+ grantResults: IntArray) {
+ if (requestCode == REQUEST_CAMERA_PERMISSION) {
+ if (grantResults.size != 1 || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
+ ErrorDialog.newInstance(getString(R.string.request_permission))
+ .show(childFragmentManager, FRAGMENT_DIALOG)
+ }
+ } else {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+ }
+ }
+
+ /**
+ * Sets up member variables related to camera.
+ *
+ * @param width The width of available size for camera preview
+ * @param height The height of available size for camera preview
+ */
+ private fun setUpCameraOutputs(width: Int, height: Int) {
+ val manager =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ activity!!.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ try {
+ for (cameraId in if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ manager.cameraIdList
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }) {
+ val characteristics = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ manager.getCameraCharacteristics(cameraId)
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+
+ // We don't use a front facing camera in this sample.
+ val cameraDirection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ characteristics.get(CameraCharacteristics.LENS_FACING)
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+ if (cameraDirection != null &&
+ cameraDirection == CameraCharacteristics.LENS_FACING_FRONT) {
+ continue
+ }
+
+ val map = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ characteristics.get(
+ CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+
+ // For still image captures, we use the largest available size.
+ val largest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ Collections.max(
+ Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)),
+ CompareSizesByArea())
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ imageReader = ImageReader.newInstance(largest.width, largest.height,
+ ImageFormat.JPEG, /*maxImages*/ 2).apply {
+ setOnImageAvailableListener(onImageAvailableListener, backgroundHandler)
+ }
+ }
+
+ // Find out if we need to swap dimension to get the preview size relative to sensor
+ // coordinate.
+ val displayRotation = activity!!.windowManager.defaultDisplay.rotation
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
+ }
+ val swappedDimensions = areDimensionsSwapped(displayRotation)
+
+ val displaySize = Point()
+ activity!!.windowManager.defaultDisplay.getSize(displaySize)
+ val rotatedPreviewWidth = if (swappedDimensions) height else width
+ val rotatedPreviewHeight = if (swappedDimensions) width else height
+ var maxPreviewWidth = if (swappedDimensions) displaySize.y else displaySize.x
+ var maxPreviewHeight = if (swappedDimensions) displaySize.x else displaySize.y
+
+ if (maxPreviewWidth > MAX_PREVIEW_WIDTH) maxPreviewWidth = MAX_PREVIEW_WIDTH
+ if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) maxPreviewHeight = MAX_PREVIEW_HEIGHT
+
+ // Danger, W.R.! Attempting to use too large a preview size could exceed the camera
+ // bus' bandwidth limitation, resulting in gorgeous previews but the storage of
+ // garbage capture data.
+ previewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture::class.java),
+ rotatedPreviewWidth, rotatedPreviewHeight,
+ maxPreviewWidth, maxPreviewHeight,
+ largest)
+
+ // We fit the aspect ratio of TextureView to the size of preview we picked.
+ if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ textureView.setAspectRatio(previewSize.width, previewSize.height)
+ } else {
+ textureView.setAspectRatio(previewSize.height, previewSize.width)
+ }
+
+ // Check if the flash is supported.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ flashSupported =
+ characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) == true
+ }
+
+ this.cameraId = cameraId
+
+ // We've found a viable camera and finished setting up member variables,
+ // so we don't need to iterate through other available cameras.
+ return
+ }
+ } catch (e: CameraAccessException) {
+ Log.e(TAG, e.toString())
+ } catch (e: NullPointerException) {
+ // Currently an NPE is thrown when the Camera2API is used but not supported on the
+ // device this code runs.
+ ErrorDialog.newInstance(getString(R.string.camera_error))
+ .show(childFragmentManager, FRAGMENT_DIALOG)
+ }
+ }
+
+ }
+
+ /**
+ * Determines if the dimensions are swapped given the phone's current rotation.
+ *
+ * @param displayRotation The current rotation of the display
+ *
+ * @return true if the dimensions are swapped, false otherwise.
+ */
+ private fun areDimensionsSwapped(displayRotation: Int): Boolean {
+ var swappedDimensions = false
+ when (displayRotation) {
+ Surface.ROTATION_0, Surface.ROTATION_180 -> {
+ if (sensorOrientation == 90 || sensorOrientation == 270) {
+ swappedDimensions = true
+ }
+ }
+ Surface.ROTATION_90, Surface.ROTATION_270 -> {
+ if (sensorOrientation == 0 || sensorOrientation == 180) {
+ swappedDimensions = true
+ }
+ }
+ else -> {
+ Log.e(TAG, "Display rotation is invalid: $displayRotation")
+ }
+ }
+ return swappedDimensions
+ }
+
+ /**
+ * Opens the camera specified by [Camera2BasicFragment.cameraId].
+ */
+ private fun openCamera(width: Int, height: Int) {
+ val permission = ContextCompat.checkSelfPermission(this.activity!!, Manifest.permission.CAMERA)
+ if (permission != PackageManager.PERMISSION_GRANTED) {
+ requestCameraPermission()
+ return
+ }
+ setUpCameraOutputs(width, height)
+ configureTransform(width, height)
+ val manager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ activity!!.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ try {
+ // Wait for camera to open - 2.5 seconds is sufficient
+ if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
+ throw RuntimeException("Time out waiting to lock camera opening.")
+ }
+ manager.openCamera(cameraId, stateCallback, backgroundHandler)
+ } catch (e: CameraAccessException) {
+ Log.e(TAG, e.toString())
+ } catch (e: InterruptedException) {
+ throw RuntimeException("Interrupted while trying to lock camera opening.", e)
+ }
+ }
+
+ }
+
+ /**
+ * Closes the current [CameraDevice].
+ */
+ private fun closeCamera() {
+ try {
+ cameraOpenCloseLock.acquire()
+ captureSession?.close()
+ captureSession = null
+ cameraDevice?.close()
+ cameraDevice = null
+ imageReader?.close()
+ imageReader = null
+ } catch (e: InterruptedException) {
+ throw RuntimeException("Interrupted while trying to lock camera closing.", e)
+ } finally {
+ cameraOpenCloseLock.release()
+ }
+ }
+
+ /**
+ * Starts a background thread and its [Handler].
+ */
+ private fun startBackgroundThread() {
+ backgroundThread = HandlerThread("CameraBackground").also { it.start() }
+ backgroundHandler = Handler(backgroundThread?.looper)
+ }
+
+ /**
+ * Stops the background thread and its [Handler].
+ */
+ private fun stopBackgroundThread() {
+ backgroundThread?.quitSafely()
+ try {
+ backgroundThread?.join()
+ backgroundThread = null
+ backgroundHandler = null
+ } catch (e: InterruptedException) {
+ Log.e(TAG, e.toString())
+ }
+
+ }
+
+ /**
+ * Creates a new [CameraCaptureSession] for camera preview.
+ */
+ private fun createCameraPreviewSession() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ try {
+ val texture = textureView.surfaceTexture
+
+ // We configure the size of default buffer to be the size of camera preview we want.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ texture.setDefaultBufferSize(previewSize.width, previewSize.height)
+ }
+
+ // This is the output Surface we need to start preview.
+ val surface = Surface(texture)
+
+ // We set up a CaptureRequest.Builder with the output Surface.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ previewRequestBuilder = cameraDevice!!.createCaptureRequest(
+ CameraDevice.TEMPLATE_PREVIEW
+ )
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ previewRequestBuilder.addTarget(surface)
+ }
+
+ // Here, we create a CameraCaptureSession for camera preview.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ cameraDevice?.createCaptureSession(Arrays.asList(surface, imageReader?.surface),
+ object : CameraCaptureSession.StateCallback() {
+
+ override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
+ // The camera is already closed
+ if (cameraDevice == null) return
+
+ // When the session is ready, we start displaying the preview.
+ captureSession = cameraCaptureSession
+ try {
+ // Auto focus should be continuous for camera preview.
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+ // Flash is automatically enabled when necessary.
+ setAutoFlash(previewRequestBuilder)
+
+ // Finally, we start displaying the camera preview.
+ previewRequest = previewRequestBuilder.build()
+ captureSession?.setRepeatingRequest(previewRequest,
+ captureCallback, backgroundHandler)
+ } catch (e: CameraAccessException) {
+ Log.e(TAG, e.toString())
+ }
+
+ }
+
+ override fun onConfigureFailed(session: CameraCaptureSession) {
+ activity!!.showToast("Failed")
+ }
+ }, null)
+ }
+ } catch (e: CameraAccessException) {
+ Log.e(TAG, e.toString())
+ }
+ }
+
+ }
+
+ /**
+ * Configures the necessary [android.graphics.Matrix] transformation to `textureView`.
+ * This method should be called after the camera preview size is determined in
+ * setUpCameraOutputs and also the size of `textureView` is fixed.
+ *
+ * @param viewWidth The width of `textureView`
+ * @param viewHeight The height of `textureView`
+ */
+ private fun configureTransform(viewWidth: Int, viewHeight: Int) {
+ activity ?: return
+ val rotation = activity!!.windowManager.defaultDisplay.rotation
+ val matrix = Matrix()
+ val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
+ val bufferRect = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ RectF(0f, 0f, previewSize.height.toFloat(), previewSize.width.toFloat())
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+ val centerX = viewRect.centerX()
+ val centerY = viewRect.centerY()
+
+ if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
+ bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
+ val scale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ Math.max(
+ viewHeight.toFloat() / previewSize.height,
+ viewWidth.toFloat() / previewSize.width)
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+ with(matrix) {
+ setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
+ postScale(scale, scale, centerX, centerY)
+ postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
+ }
+ } else if (Surface.ROTATION_180 == rotation) {
+ matrix.postRotate(180f, centerX, centerY)
+ }
+ textureView.setTransform(matrix)
+ }
+
+ /**
+ * Lock the focus as the first step for a still image capture.
+ */
+ private fun lockFocus() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ try {
+ // This is how to tell the camera to lock focus.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START)
+ }
+ // Tell #captureCallback to wait for the lock.
+ state = STATE_WAITING_LOCK
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ captureSession?.capture(previewRequestBuilder.build(), captureCallback,
+ backgroundHandler)
+ }
+ } catch (e: CameraAccessException) {
+ Log.e(TAG, e.toString())
+ }
+ }
+
+ }
+
+ /**
+ * Run the precapture sequence for capturing a still image. This method should be called when
+ * we get a response in [.captureCallback] from [.lockFocus].
+ */
+ private fun runPrecaptureSequence() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ try {
+ // This is how to tell the camera to trigger.
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+ CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
+ }
+ // Tell #captureCallback to wait for the precapture sequence to be set.
+ state = STATE_WAITING_PRECAPTURE
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ captureSession?.capture(previewRequestBuilder.build(), captureCallback,
+ backgroundHandler)
+ }
+ } catch (e: CameraAccessException) {
+ Log.e(TAG, e.toString())
+ }
+ }
+
+ }
+
+ /**
+ * Capture a still picture. This method should be called when we get a response in
+ * [.captureCallback] from both [.lockFocus].
+ */
+ private fun captureStillPicture() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ try {
+ if (activity == null || cameraDevice == null) return
+ val rotation = activity!!.windowManager.defaultDisplay.rotation
+
+ // This is the CaptureRequest.Builder that we use to take a picture.
+ val captureBuilder = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ cameraDevice?.createCaptureRequest(
+ CameraDevice.TEMPLATE_STILL_CAPTURE)?.apply {
+ addTarget(imageReader?.surface)
+
+ // Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
+ // We have to take that into account and rotate JPEG properly.
+ // For devices with orientation of 90, we return our mapping from ORIENTATIONS.
+ // For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
+ set(CaptureRequest.JPEG_ORIENTATION,
+ (ORIENTATIONS.get(rotation) + sensorOrientation + 270) % 360)
+
+ // Use the same AE and AF modes as the preview.
+ set(CaptureRequest.CONTROL_AF_MODE,
+ CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+ }?.also { setAutoFlash(it) }
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+
+ val captureCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ object : CameraCaptureSession.CaptureCallback() {
+
+ override fun onCaptureCompleted(session: CameraCaptureSession,
+ request: CaptureRequest,
+ result: TotalCaptureResult) {
+ activity!!.showToast("Saved: $file")
+
+ Log.d(TAG, file.toString())
+ unlockFocus()
+ sendOutPut(file)
+
+
+ }
+ }
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+
+ captureSession?.apply {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ stopRepeating()
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ abortCaptures()
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ capture(captureBuilder?.build(), captureCallback, null)
+ }
+ }
+ } catch (e: CameraAccessException) {
+ Log.e(TAG, e.toString())
+ }
+ }
+
+ }
+
+ private fun sendOutPut(file: File) {
+ val i = Intent()
+ i.putExtra(IMAGE_PICKED_KEY, file.absolutePath)
+ activity!!.setResult(RESPONSE_CODE_OPEN_CAMERA, i)
+ activity!!.finish()
+
+
+ }
+
+ /**
+ * Unlock the focus. This method should be called when still image capture sequence is
+ * finished.
+ */
+ private fun unlockFocus() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ try {
+ // Reset the auto-focus trigger
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ previewRequestBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+ CameraMetadata.CONTROL_AF_TRIGGER_CANCEL)
+ }
+ setAutoFlash(previewRequestBuilder)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ captureSession?.capture(previewRequestBuilder.build(), captureCallback,
+ backgroundHandler)
+ }
+ // After this, the camera will go back to the normal state of preview.
+ state = STATE_PREVIEW
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ captureSession?.setRepeatingRequest(previewRequest, captureCallback,
+ backgroundHandler)
+ }
+ } catch (e: CameraAccessException) {
+ Log.e(TAG, e.toString())
+ }
+ }
+
+ }
+
+ override fun onClick(view: View) {
+ when (view.id) {
+ R.id.picture -> lockFocus()
+
+ }
+ }
+
+ private fun setAutoFlash(requestBuilder: CaptureRequest.Builder) {
+ if (flashSupported) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ requestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
+ CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+ }
+ }
+ }
+
+ companion object {
+
+ /**
+ * Conversion from screen rotation to JPEG orientation.
+ */
+ private val ORIENTATIONS = SparseIntArray()
+ private val FRAGMENT_DIALOG = "dialog"
+
+ init {
+ ORIENTATIONS.append(Surface.ROTATION_0, 90)
+ ORIENTATIONS.append(Surface.ROTATION_90, 0)
+ ORIENTATIONS.append(Surface.ROTATION_180, 270)
+ ORIENTATIONS.append(Surface.ROTATION_270, 180)
+ }
+
+ /**
+ * Tag for the [Log].
+ */
+ private val TAG = "Camera2BasicFragment"
+
+ /**
+ * Camera state: Showing camera preview.
+ */
+ private val STATE_PREVIEW = 0
+
+ /**
+ * Camera state: Waiting for the focus to be locked.
+ */
+ private val STATE_WAITING_LOCK = 1
+
+ /**
+ * Camera state: Waiting for the exposure to be precapture state.
+ */
+ private val STATE_WAITING_PRECAPTURE = 2
+
+ /**
+ * Camera state: Waiting for the exposure state to be something other than precapture.
+ */
+ private val STATE_WAITING_NON_PRECAPTURE = 3
+
+ /**
+ * Camera state: Picture was taken.
+ */
+ private val STATE_PICTURE_TAKEN = 4
+
+ /**
+ * Max preview width that is guaranteed by Camera2 API
+ */
+ private val MAX_PREVIEW_WIDTH = 1920
+
+ /**
+ * Max preview height that is guaranteed by Camera2 API
+ */
+ private val MAX_PREVIEW_HEIGHT = 1080
+
+ /**
+ * Given `choices` of `Size`s supported by a camera, choose the smallest one that
+ * is at least as large as the respective texture view size, and that is at most as large as
+ * the respective max size, and whose aspect ratio matches with the specified value. If such
+ * size doesn't exist, choose the largest one that is at most as large as the respective max
+ * size, and whose aspect ratio matches with the specified value.
+ *
+ * @param choices The list of sizes that the camera supports for the intended
+ * output class
+ * @param textureViewWidth The width of the texture view relative to sensor coordinate
+ * @param textureViewHeight The height of the texture view relative to sensor coordinate
+ * @param maxWidth The maximum width that can be chosen
+ * @param maxHeight The maximum height that can be chosen
+ * @param aspectRatio The aspect ratio
+ * @return The optimal `Size`, or an arbitrary one if none were big enough
+ */
+ @JvmStatic
+ private fun chooseOptimalSize(
+ choices: Array,
+ textureViewWidth: Int,
+ textureViewHeight: Int,
+ maxWidth: Int,
+ maxHeight: Int,
+ aspectRatio: Size
+ ): Size {
+
+ // Collect the supported resolutions that are at least as big as the preview Surface
+ val bigEnough = ArrayList()
+ // Collect the supported resolutions that are smaller than the preview Surface
+ val notBigEnough = ArrayList()
+ val w = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ aspectRatio.width
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+ val h = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ aspectRatio.height
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }
+ for (option in choices) {
+ if (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ option.width <= maxWidth && option.height <= maxHeight &&
+ option.height == option.width * h / w
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }) {
+ if (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ option.width >= textureViewWidth && option.height >= textureViewHeight
+ } else {
+ TODO("VERSION.SDK_INT < LOLLIPOP")
+ }) {
+ bigEnough.add(option)
+ } else {
+ notBigEnough.add(option)
+ }
+ }
+ }
+
+ // Pick the smallest of those big enough. If there is no one big enough, pick the
+ // largest of those not big enough.
+ if (bigEnough.size > 0) {
+ return Collections.min(bigEnough, CompareSizesByArea())
+ } else if (notBigEnough.size > 0) {
+ return Collections.max(notBigEnough, CompareSizesByArea())
+ } else {
+ Log.e(TAG, "Couldn't find any suitable preview size")
+ return choices[0]
+ }
+ }
+
+ @JvmStatic
+ fun newInstance(): Camera2BasicFragment = Camera2BasicFragment()
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/CameraActivity.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/CameraActivity.kt
new file mode 100644
index 0000000..bbff17d
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/CameraActivity.kt
@@ -0,0 +1,41 @@
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraMetadata
+import android.hardware.camera2.CaptureRequest
+import android.media.Image
+import android.os.Build
+import android.os.Bundle
+import android.support.annotation.RequiresApi
+import android.support.v7.app.AppCompatActivity
+import android.view.View
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.waprofileImage.WAProfileImage
+import kotlinx.android.synthetic.main.activity_camera.*
+import java.io.File
+
+
+class CameraActivity : AppCompatActivity() {
+ @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_camera)
+ setTitle(null)
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
+ supportFragmentManager.beginTransaction()
+ .replace(R.id.container, Camera2BasicFragment.newInstance())
+ .commit()
+
+ }else
+ {
+ // TODO Handle Other DEVICES
+
+ }
+
+
+ }
+
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/Common.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/Common.kt
new file mode 100644
index 0000000..c85bd07
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/Common.kt
@@ -0,0 +1,16 @@
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+import android.content.Context
+import android.content.pm.PackageManager
+
+object Common {
+ fun checkCameraHardware(context: Context): Boolean {
+ if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
+ // this device has a camera
+ return true
+ } else {
+ // no camera on this device
+ return false
+ }
+ }
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/CompareSizesByArea.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/CompareSizesByArea.kt
new file mode 100644
index 0000000..dc596d7
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/CompareSizesByArea.kt
@@ -0,0 +1,17 @@
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+import android.util.Size
+import java.lang.Long.signum
+
+import java.util.Comparator
+
+/**
+ * Compares two `Size`s based on their areas.
+ */
+internal class CompareSizesByArea : Comparator {
+
+ // We cast here to ensure the multiplications won't overflow
+ override fun compare(lhs: Size, rhs: Size) =
+ signum(lhs.width.toLong() * lhs.height - rhs.width.toLong() * rhs.height)
+
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ConfirmationDialog.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ConfirmationDialog.kt
new file mode 100644
index 0000000..827928c
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ConfirmationDialog.kt
@@ -0,0 +1,27 @@
+
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+import android.Manifest
+import android.app.AlertDialog
+import android.app.Dialog
+import android.os.Bundle
+import android.support.v4.app.DialogFragment
+import com.mindorks.waprofileimage.R
+
+/**
+ * Shows OK/Cancel confirmation dialog about camera permission.
+ */
+class ConfirmationDialog : DialogFragment() {
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
+ AlertDialog.Builder(activity)
+ .setMessage(R.string.request_permission)
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ parentFragment!!.requestPermissions(arrayOf(Manifest.permission.CAMERA),
+ REQUEST_CAMERA_PERMISSION)
+ }
+ .setNegativeButton(android.R.string.cancel) { _, _ ->
+ parentFragment!!.activity?.finish()
+ }
+ .create()
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/Constants.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/Constants.kt
new file mode 100644
index 0000000..3337778
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/Constants.kt
@@ -0,0 +1,6 @@
+@file:JvmName("Constants")
+
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+@JvmField val REQUEST_CAMERA_PERMISSION = 1
+@JvmField val PIC_FILE_NAME = "pic.jpg"
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ErrorDialog.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ErrorDialog.kt
new file mode 100644
index 0000000..1717503
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ErrorDialog.kt
@@ -0,0 +1,30 @@
+
+
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.os.Bundle
+import android.support.v4.app.DialogFragment
+
+/**
+ * Shows an error message dialog.
+ */
+class ErrorDialog : DialogFragment() {
+
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog =
+ AlertDialog.Builder(activity)
+ .setMessage(arguments!!.getString(ARG_MESSAGE))
+ .setPositiveButton(android.R.string.ok) { _, _ -> activity!!.finish() }
+ .create()
+
+ companion object {
+
+ @JvmStatic private val ARG_MESSAGE = "message"
+
+ @JvmStatic fun newInstance(message: String): ErrorDialog = ErrorDialog().apply {
+ arguments = Bundle().apply { putString(ARG_MESSAGE, message) }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ImageSaver.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ImageSaver.kt
new file mode 100644
index 0000000..bceac1b
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/camera/ImageSaver.kt
@@ -0,0 +1,57 @@
+package com.mindorks.waprofileimage.waprofileImage.camera
+
+import android.media.Image
+import android.util.Log
+import android.webkit.ValueCallback
+
+import java.io.File
+import java.io.FileOutputStream
+import java.io.IOException
+
+/**
+ * Saves a JPEG [Image] into the specified [File].
+ */
+internal class ImageSaver(
+ /**
+ * The JPEG image
+ */
+ private val image: Image,
+
+ /**
+ * The file we save the image into.
+ */
+ private val file: File
+,val isSuccessCallback :ValueCallback) : Runnable {
+
+ override fun run() {
+ val buffer = image.planes[0].buffer
+ val bytes = ByteArray(buffer.remaining())
+ buffer.get(bytes)
+ var output: FileOutputStream? = null
+ try {
+ output = FileOutputStream(file).apply { write(bytes)
+ isSuccessCallback.onReceiveValue(true)
+ }
+ } catch (e: IOException) {
+ Log.e(TAG, e.toString())
+ isSuccessCallback.onReceiveValue(false)
+
+ } finally {
+ image.close()
+ output?.let {
+ try {
+ it.close()
+ } catch (e: IOException) {
+ Log.e(TAG, e.toString())
+ }
+ }
+ }
+ }
+
+ companion object {
+ /**
+ * Tag for the [Log].
+ */
+ private val TAG = "ImageSaver"
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/MainActivity.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/MainActivity.kt
new file mode 100644
index 0000000..03d69a9
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/MainActivity.kt
@@ -0,0 +1,588 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.activities
+
+import android.app.Activity
+import android.content.Intent
+import android.hardware.SensorManager
+import android.net.Uri
+import android.os.Bundle
+import android.os.Handler
+import android.provider.MediaStore
+import android.view.*
+import android.widget.RelativeLayout
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
+import com.bumptech.glide.request.RequestOptions
+import com.mindorks.waprofileimage.BuildConfig
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.config
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.getMyCamera
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.navBarHeight
+import com.mindorks.waprofileimage.waprofileImage.newcam.helpers.*
+import com.mindorks.waprofileimage.waprofileImage.newcam.interfaces.MyCamera
+import com.mindorks.waprofileimage.waprofileImage.newcam.interfaces.MyPreview
+import com.mindorks.waprofileimage.waprofileImage.newcam.views.AutoFitTextureView
+import com.mindorks.waprofileimage.waprofileImage.newcam.views.FocusCircleView
+import com.mindorks.waprofileimage.waprofileImage.newcam.views.PreviewCameraOne
+import com.mindorks.waprofileimage.waprofileImage.newcam.views.PreviewCameraTwo
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.*
+import com.simplemobiletools.commons.models.Release
+import kotlinx.android.synthetic.main.activity_main.*
+
+class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener {
+ private val FADE_DELAY = 5000L
+
+ lateinit var mTimerHandler: Handler
+ private lateinit var mOrientationEventListener: OrientationEventListener
+ private lateinit var mFocusCircleView: FocusCircleView
+ private lateinit var mFadeHandler: Handler
+ private lateinit var mCameraImpl: MyCamera
+
+ private var mPreview: MyPreview? = null
+ private var mPreviewUri: Uri? = null
+ private var mIsInPhotoMode = false
+ private var mIsCameraAvailable = false
+ private var mIsVideoCaptureIntent = false
+ private var mIsHardwareShutterHandled = false
+ private var mCurrVideoRecTimer = 0
+ var mLastHandledOrientation = 0
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ window.addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
+ WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
+ WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
+ WindowManager.LayoutParams.FLAG_FULLSCREEN)
+
+ useDynamicTheme = false
+ super.onCreate(savedInstanceState)
+ appLaunched(BuildConfig.APPLICATION_ID)
+ requestWindowFeature(Window.FEATURE_NO_TITLE)
+
+ initVariables()
+ tryInitCamera()
+ supportActionBar?.hide()
+ checkWhatsNewDialog()
+ setupOrientationEventListener()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (hasStorageAndCameraPermissions()) {
+ mPreview?.onResumed()
+ resumeCameraItems()
+ setupPreviewImage(mIsInPhotoMode)
+ scheduleFadeOut()
+ mFocusCircleView.setStrokeColor(getAdjustedPrimaryColor())
+
+ if (mIsVideoCaptureIntent && mIsInPhotoMode) {
+ handleTogglePhotoVideo()
+ checkButtons()
+ }
+ toggleBottomButtons(false)
+ }
+ window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ if (hasStorageAndCameraPermissions()) {
+ mOrientationEventListener.enable()
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+ if (!hasStorageAndCameraPermissions() || isAskingPermissions) {
+ return
+ }
+
+ mFadeHandler.removeCallbacksAndMessages(null)
+
+ hideTimer()
+ mOrientationEventListener.disable()
+
+ if (mPreview?.getCameraState() == STATE_PICTURE_TAKEN) {
+ toast(R.string.photo_not_saved)
+ }
+ mPreview?.onPaused()
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ mPreview = null
+ }
+
+ private fun initVariables() {
+ mIsInPhotoMode = config.initPhotoMode
+ mIsCameraAvailable = false
+ mIsVideoCaptureIntent = false
+ mIsHardwareShutterHandled = false
+ mCurrVideoRecTimer = 0
+ mLastHandledOrientation = 0
+ mCameraImpl = getMyCamera()
+
+ if (config.alwaysOpenBackCamera) {
+ config.lastUsedCamera = mCameraImpl.getBackCameraId().toString()
+ }
+ }
+
+ override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
+ return if (keyCode == KeyEvent.KEYCODE_CAMERA && !mIsHardwareShutterHandled) {
+ mIsHardwareShutterHandled = true
+ shutterPressed()
+ true
+ } else if (!mIsHardwareShutterHandled && config.volumeButtonsAsShutter && (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)) {
+ mIsHardwareShutterHandled = true
+ shutterPressed()
+ true
+ } else {
+ super.onKeyDown(keyCode, event)
+ }
+ }
+
+ override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
+ if (keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
+ mIsHardwareShutterHandled = false
+ }
+ return super.onKeyUp(keyCode, event)
+ }
+
+ private fun hideIntentButtons() {
+ toggle_photo_video.beGone()
+ settings.beGone()
+ last_photo_video_preview.beGone()
+ }
+
+ private fun tryInitCamera() {
+ handlePermission(PERMISSION_CAMERA) {
+ if (it) {
+ handlePermission(PERMISSION_WRITE_STORAGE) {
+ if (it) {
+ initializeCamera()
+ } else {
+ toast(R.string.no_storage_permissions)
+ finish()
+ }
+ }
+ } else {
+ toast(R.string.no_camera_permissions)
+ finish()
+ }
+ }
+ }
+
+ private fun isImageCaptureIntent() = intent?.action == MediaStore.ACTION_IMAGE_CAPTURE || intent?.action == MediaStore.ACTION_IMAGE_CAPTURE_SECURE
+
+ private fun checkImageCaptureIntent() {
+ if (isImageCaptureIntent()) {
+ hideIntentButtons()
+ val output = intent.extras?.get(MediaStore.EXTRA_OUTPUT)
+ if (output != null && output is Uri) {
+ mPreview?.setTargetUri(output)
+ }
+ }
+ }
+
+ private fun checkVideoCaptureIntent() {
+ if (intent?.action == MediaStore.ACTION_VIDEO_CAPTURE) {
+ mIsVideoCaptureIntent = true
+ mIsInPhotoMode = false
+ hideIntentButtons()
+ shutter.setImageResource(R.drawable.ic_video_rec)
+ }
+ }
+
+ private fun initializeCamera() {
+ setContentView(R.layout.activity_main)
+ initButtons()
+
+ camera_surface_view.beVisibleIf(!isLollipopPlus())
+ camera_texture_view.beVisibleIf(isLollipopPlus())
+
+ (btn_holder.layoutParams as RelativeLayout.LayoutParams).setMargins(0, 0, 0, (navBarHeight + resources.getDimension(R.dimen.activity_margin)).toInt())
+
+ checkVideoCaptureIntent()
+ mPreview = if (isLollipopPlus()) PreviewCameraTwo(this, camera_texture_view as AutoFitTextureView, mIsInPhotoMode) else PreviewCameraOne(this, camera_surface_view)
+ view_holder.addView(mPreview as ViewGroup)
+ checkImageCaptureIntent()
+ mPreview?.setIsImageCaptureIntent(isImageCaptureIntent())
+
+ val imageDrawable = if (config.lastUsedCamera == mCameraImpl.getBackCameraId().toString()) R.drawable.ic_camera_front else R.drawable.ic_camera_rear
+ toggle_camera.setImageResource(imageDrawable)
+
+ mFocusCircleView = FocusCircleView(applicationContext)
+ view_holder.addView(mFocusCircleView)
+
+ mTimerHandler = Handler()
+ mFadeHandler = Handler()
+ setupPreviewImage(true)
+
+ val initialFlashlightState = if (config.turnFlashOffAtStartup) FLASH_OFF else config.flashlightState
+ mPreview!!.setFlashlightState(initialFlashlightState)
+ updateFlashlightState(initialFlashlightState)
+ }
+
+ private fun initButtons() {
+ toggle_camera.setOnClickListener { toggleCamera() }
+ last_photo_video_preview.setOnClickListener { showLastMediaPreview() }
+ toggle_flash.setOnClickListener { toggleFlash() }
+ shutter.setOnClickListener { shutterPressed() }
+ settings.setOnClickListener { launchSettings() }
+ toggle_photo_video.setOnClickListener { handleTogglePhotoVideo() }
+ change_resolution.setOnClickListener { mPreview?.showChangeResolutionDialog() }
+ }
+
+ private fun toggleCamera() {
+ if (checkCameraAvailable()) {
+ mPreview!!.toggleFrontBackCamera()
+ }
+ }
+
+ private fun showLastMediaPreview() {
+ if (mPreviewUri != null) {
+ val path = applicationContext.getRealPathFromURI(mPreviewUri!!)
+ ?: mPreviewUri!!.toString()
+ openPathIntent(path, false, BuildConfig.APPLICATION_ID)
+ }
+ }
+
+ private fun toggleFlash() {
+ if (checkCameraAvailable()) {
+ mPreview?.toggleFlashlight()
+ }
+ }
+
+ fun updateFlashlightState(state: Int) {
+ config.flashlightState = state
+ val flashDrawable = when (state) {
+ FLASH_OFF -> R.drawable.ic_flash_off
+ FLASH_ON -> R.drawable.ic_flash_on
+ else -> R.drawable.ic_flash_auto
+ }
+ toggle_flash.setImageResource(flashDrawable)
+ }
+
+ fun updateCameraIcon(isUsingFrontCamera: Boolean) {
+ toggle_camera.setImageResource(if (isUsingFrontCamera) R.drawable.ic_camera_rear else R.drawable.ic_camera_front)
+ }
+
+ private fun shutterPressed() {
+ if (checkCameraAvailable()) {
+ handleShutter()
+ }
+ }
+
+ private fun handleShutter() {
+ if (mIsInPhotoMode) {
+ toggleBottomButtons(true)
+ mPreview?.tryTakePicture()
+ } else {
+ mPreview?.toggleRecording()
+ }
+ }
+
+ fun toggleBottomButtons(hide: Boolean) {
+ runOnUiThread {
+ val alpha = if (hide) 0f else 1f
+ shutter.animate().alpha(alpha).start()
+ toggle_camera.animate().alpha(alpha).start()
+ toggle_flash.animate().alpha(alpha).start()
+
+ shutter.isClickable = !hide
+ toggle_camera.isClickable = !hide
+ toggle_flash.isClickable = !hide
+ }
+ }
+
+ private fun launchSettings() {
+ if (settings.alpha == 1f) {
+ val intent = Intent(applicationContext, SettingsActivity::class.java)
+ startActivity(intent)
+ } else {
+ fadeInButtons()
+ }
+ }
+
+ private fun handleTogglePhotoVideo() {
+ handlePermission(PERMISSION_RECORD_AUDIO) {
+ if (it) {
+ togglePhotoVideo()
+ } else {
+ toast(R.string.no_audio_permissions)
+ if (mIsVideoCaptureIntent) {
+ finish()
+ }
+ }
+ }
+ }
+
+ private fun togglePhotoVideo() {
+ if (!checkCameraAvailable()) {
+ return
+ }
+
+ if (mIsVideoCaptureIntent) {
+ mPreview?.tryInitVideoMode()
+ }
+
+ mPreview?.setFlashlightState(FLASH_OFF)
+ hideTimer()
+ mIsInPhotoMode = !mIsInPhotoMode
+ config.initPhotoMode = mIsInPhotoMode
+ showToggleCameraIfNeeded()
+ checkButtons()
+ toggleBottomButtons(false)
+ }
+
+ private fun checkButtons() {
+ if (mIsInPhotoMode) {
+ initPhotoMode()
+ } else {
+ tryInitVideoMode()
+ }
+ }
+
+ private fun initPhotoMode() {
+ toggle_photo_video.setImageResource(R.drawable.ic_video)
+ shutter.setImageResource(R.drawable.ic_shutter)
+ mPreview?.initPhotoMode()
+ setupPreviewImage(true)
+ }
+
+ private fun tryInitVideoMode() {
+ if (mPreview?.initVideoMode() == true) {
+ initVideoButtons()
+ } else {
+ if (!mIsVideoCaptureIntent) {
+ toast(R.string.video_mode_error)
+ }
+ }
+ }
+
+ private fun initVideoButtons() {
+ toggle_photo_video.setImageResource(R.drawable.ic_camera)
+ showToggleCameraIfNeeded()
+ shutter.setImageResource(R.drawable.ic_video_rec)
+ setupPreviewImage(false)
+ mPreview?.checkFlashlight()
+ }
+
+ private fun setupPreviewImage(isPhoto: Boolean) {
+ val uri = if (isPhoto) MediaStore.Images.Media.EXTERNAL_CONTENT_URI else MediaStore.Video.Media.EXTERNAL_CONTENT_URI
+ val lastMediaId = getLatestMediaId(uri)
+ if (lastMediaId == 0L) {
+ return
+ }
+
+ mPreviewUri = Uri.withAppendedPath(uri, lastMediaId.toString())
+
+ runOnUiThread {
+ if (!isActivityDestroyed()) {
+ val options = RequestOptions()
+ .centerCrop()
+ .diskCacheStrategy(DiskCacheStrategy.NONE)
+
+ Glide.with(this)
+ .load(mPreviewUri)
+ .apply(options)
+ .transition(DrawableTransitionOptions.withCrossFade())
+ .into(last_photo_video_preview)
+ }
+ }
+ }
+
+ private fun scheduleFadeOut() {
+ if (!config.keepSettingsVisible) {
+ mFadeHandler.postDelayed({
+ fadeOutButtons()
+ }, FADE_DELAY)
+ }
+ }
+
+ private fun fadeOutButtons() {
+ fadeAnim(settings, .5f)
+ fadeAnim(toggle_photo_video, .0f)
+ fadeAnim(change_resolution, .0f)
+ fadeAnim(last_photo_video_preview, .0f)
+ }
+
+ private fun fadeInButtons() {
+ fadeAnim(settings, 1f)
+ fadeAnim(toggle_photo_video, 1f)
+ fadeAnim(change_resolution, 1f)
+ fadeAnim(last_photo_video_preview, 1f)
+ scheduleFadeOut()
+ }
+
+ private fun fadeAnim(view: View, value: Float) {
+ view.animate().alpha(value).start()
+ view.isClickable = value != .0f
+ }
+
+ private fun hideNavigationBarIcons() {
+ window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE
+ }
+
+ fun toggleTimer(show: Boolean) {
+ if (show) {
+ showTimer()
+ } else {
+ hideTimer()
+ }
+ }
+
+ private fun showTimer() {
+ video_rec_curr_timer.beVisible()
+ setupTimer()
+ }
+
+ private fun hideTimer() {
+ video_rec_curr_timer.text = 0.getFormattedDuration()
+ video_rec_curr_timer.beGone()
+ mCurrVideoRecTimer = 0
+ mTimerHandler.removeCallbacksAndMessages(null)
+ }
+
+ private fun setupTimer() {
+ runOnUiThread(object : Runnable {
+ override fun run() {
+ video_rec_curr_timer.text = mCurrVideoRecTimer++.getFormattedDuration()
+ mTimerHandler.postDelayed(this, 1000L)
+ }
+ })
+ }
+
+ private fun resumeCameraItems() {
+ showToggleCameraIfNeeded()
+ if (mPreview?.resumeCamera() == true) {
+ hideNavigationBarIcons()
+
+ if (!mIsInPhotoMode) {
+ initVideoButtons()
+ }
+ } else {
+ toast(R.string.camera_switch_error)
+ }
+ }
+
+ private fun showToggleCameraIfNeeded() {
+ toggle_camera?.beInvisibleIf(mCameraImpl.getCountOfCameras() <= 1)
+ }
+
+ private fun hasStorageAndCameraPermissions() = hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA)
+
+ private fun setupOrientationEventListener() {
+ mOrientationEventListener = object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
+ override fun onOrientationChanged(orientation: Int) {
+ if (isActivityDestroyed()) {
+ mOrientationEventListener.disable()
+ return
+ }
+
+ val currOrient = when (orientation) {
+ in 75..134 -> ORIENT_LANDSCAPE_RIGHT
+ in 225..289 -> ORIENT_LANDSCAPE_LEFT
+ else -> ORIENT_PORTRAIT
+ }
+
+ if (currOrient != mLastHandledOrientation) {
+ val degrees = when (currOrient) {
+ ORIENT_LANDSCAPE_LEFT -> 90
+ ORIENT_LANDSCAPE_RIGHT -> -90
+ else -> 0
+ }
+
+ animateViews(degrees)
+ mLastHandledOrientation = currOrient
+ mPreview?.deviceOrientationChanged()
+ }
+ }
+ }
+ }
+
+ private fun animateViews(degrees: Int) {
+ val views = arrayOf(
+ toggle_camera, toggle_flash, toggle_photo_video, change_resolution, shutter, settings, last_photo_video_preview)
+ for (view in views) {
+ rotate(view, degrees)
+ }
+ }
+
+ private fun rotate(view: View, degrees: Int) = view.animate().rotation(degrees.toFloat()).start()
+
+ private fun checkCameraAvailable(): Boolean {
+ if (!mIsCameraAvailable) {
+ toast(R.string.camera_unavailable)
+ }
+ return mIsCameraAvailable
+ }
+
+ fun setFlashAvailable(available: Boolean) {
+ if (available) {
+ toggle_flash.beVisible()
+ } else {
+ toggle_flash.beInvisible()
+ toggle_flash.setImageResource(R.drawable.ic_flash_off)
+ mPreview?.setFlashlightState(FLASH_OFF)
+ }
+ }
+
+ fun setIsCameraAvailable(available: Boolean) {
+ mIsCameraAvailable = available
+ }
+
+ fun setRecordingState(isRecording: Boolean) {
+ runOnUiThread {
+ if (isRecording) {
+ shutter.setImageResource(R.drawable.ic_video_stop)
+ toggle_camera.beInvisible()
+ showTimer()
+ } else {
+ shutter.setImageResource(R.drawable.ic_video_rec)
+ showToggleCameraIfNeeded()
+ hideTimer()
+ }
+ }
+ }
+
+ fun videoSaved(uri: Uri) {
+ setupPreviewImage(false)
+ if (mIsVideoCaptureIntent) {
+ Intent().apply {
+ data = uri
+ flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ setResult(Activity.RESULT_OK, this)
+ }
+ finish()
+ }
+ }
+
+ fun drawFocusCircle(x: Float, y: Float) = mFocusCircleView.drawFocusCircle(x, y)
+
+ override fun mediaSaved(path: String) {
+ mPreview?.imageSaved()
+ rescanPaths(arrayListOf(path)) {
+ setupPreviewImage(true)
+ Intent(BROADCAST_REFRESH_MEDIA).apply {
+ putExtra(REFRESH_PATH, path)
+ `package` = "com.simplemobiletools.gallery"
+ sendBroadcast(this)
+ }
+ }
+
+ if (isImageCaptureIntent()) {
+ setResult(Activity.RESULT_OK)
+ finish()
+ }
+ }
+
+ private fun checkWhatsNewDialog() {
+ arrayListOf().apply {
+ add(Release(33, R.string.release_33))
+ add(Release(35, R.string.release_35))
+ add(Release(39, R.string.release_39))
+ add(Release(44, R.string.release_44))
+ add(Release(46, R.string.release_46))
+ add(Release(52, R.string.release_52))
+ checkWhatsNew(this, BuildConfig.VERSION_CODE)
+ }
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/SettingsActivity.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/SettingsActivity.kt
new file mode 100644
index 0000000..6130ecb
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/SettingsActivity.kt
@@ -0,0 +1,226 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.activities
+
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import com.mindorks.waprofileimage.BuildConfig
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.config
+import com.simplemobiletools.commons.dialogs.RadioGroupDialog
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.LICENSE_GLIDE
+import com.simplemobiletools.commons.helpers.LICENSE_LEAK_CANARY
+import com.simplemobiletools.commons.helpers.isLollipopPlus
+import com.simplemobiletools.commons.models.FAQItem
+import com.simplemobiletools.commons.models.RadioItem
+import kotlinx.android.synthetic.main.activity_settings.*
+import java.util.*
+
+class SettingsActivity : SimpleActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_settings)
+ }
+
+ override fun onResume() {
+ super.onResume()
+
+ setupPurchaseThankYou()
+ setupCustomizeColors()
+ setupUseEnglish()
+ setupAvoidWhatsNew()
+ setupShowPreview()
+ setupSound()
+ setupFocusBeforeCapture()
+ setupVolumeButtonsAsShutter()
+ setupTurnFlashOffAtStartup()
+ setupFlipPhotos()
+ setupKeepSettingsVisible()
+ setupAlwaysOpenBackCamera()
+ setupSavePhotoMetadata()
+ setupSavePhotosFolder()
+ setupPhotoQuality()
+ updateTextColors(settings_holder)
+ setupSectionColors()
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu): Boolean {
+ menuInflater.inflate(R.menu.menu, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.about -> launchAbout()
+ else -> super.onOptionsItemSelected(item)
+ }
+ return true
+ }
+
+ private fun setupSectionColors() {
+ val adjustedPrimaryColor = getAdjustedPrimaryColor()
+ arrayListOf(shutter_label, startup_label, saving_label).forEach {
+ it.setTextColor(adjustedPrimaryColor)
+ }
+ }
+
+ private fun setupPurchaseThankYou() {
+ settings_purchase_thank_you_holder.beVisibleIf(config.appRunCount > 10 && !isThankYouInstalled())
+ settings_purchase_thank_you_holder.setOnClickListener {
+ launchPurchaseThankYouIntent()
+ }
+ }
+
+ private fun setupCustomizeColors() {
+ settings_customize_colors_holder.setOnClickListener {
+ startCustomizationActivity()
+ }
+ }
+
+ private fun setupUseEnglish() {
+ settings_use_english_holder.beVisibleIf(config.wasUseEnglishToggled || Locale.getDefault().language != "en")
+ settings_use_english.isChecked = config.useEnglish
+ settings_use_english_holder.setOnClickListener {
+ settings_use_english.toggle()
+ config.useEnglish = settings_use_english.isChecked
+ System.exit(0)
+ }
+ }
+
+ private fun setupAvoidWhatsNew() {
+ settings_avoid_whats_new.isChecked = config.avoidWhatsNew
+ settings_avoid_whats_new_holder.setOnClickListener {
+ settings_avoid_whats_new.toggle()
+ config.avoidWhatsNew = settings_avoid_whats_new.isChecked
+ }
+ }
+
+ private fun launchAbout() {
+ val licenses = LICENSE_GLIDE or LICENSE_LEAK_CANARY
+
+ val faqItems = arrayListOf(
+ FAQItem(R.string.faq_1_title, R.string.faq_1_text),
+ FAQItem(R.string.faq_2_title_commons, R.string.faq_2_text_commons)
+ )
+
+ startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true)
+ }
+
+ private fun getLastPart(path: String): String {
+ val humanized = humanizePath(path)
+ return humanized.substringAfterLast("/", humanized)
+ }
+
+ private fun setupShowPreview() {
+ settings_show_preview_holder.beVisibleIf(!isLollipopPlus())
+ settings_show_preview.isChecked = config.isShowPreviewEnabled
+ settings_show_preview_holder.setOnClickListener {
+ settings_show_preview.toggle()
+ config.isShowPreviewEnabled = settings_show_preview.isChecked
+ }
+ }
+
+ private fun setupSound() {
+ settings_sound.isChecked = config.isSoundEnabled
+ settings_sound_holder.setOnClickListener {
+ settings_sound.toggle()
+ config.isSoundEnabled = settings_sound.isChecked
+ }
+ }
+
+ private fun setupFocusBeforeCapture() {
+ settings_focus_before_capture.isChecked = config.focusBeforeCapture
+ settings_focus_before_capture_holder.setOnClickListener {
+ settings_focus_before_capture.toggle()
+ config.focusBeforeCapture = settings_focus_before_capture.isChecked
+ }
+ }
+
+ private fun setupVolumeButtonsAsShutter() {
+ settings_volume_buttons_as_shutter.isChecked = config.volumeButtonsAsShutter
+ settings_volume_buttons_as_shutter_holder.setOnClickListener {
+ settings_volume_buttons_as_shutter.toggle()
+ config.volumeButtonsAsShutter = settings_volume_buttons_as_shutter.isChecked
+ }
+ }
+
+ private fun setupTurnFlashOffAtStartup() {
+ settings_turn_flash_off_at_startup.isChecked = config.turnFlashOffAtStartup
+ settings_turn_flash_off_at_startup_holder.setOnClickListener {
+ settings_turn_flash_off_at_startup.toggle()
+ config.turnFlashOffAtStartup = settings_turn_flash_off_at_startup.isChecked
+ }
+ }
+
+ private fun setupFlipPhotos() {
+ settings_flip_photos.isChecked = config.flipPhotos
+ settings_flip_photos_holder.setOnClickListener {
+ settings_flip_photos.toggle()
+ config.flipPhotos = settings_flip_photos.isChecked
+ }
+ }
+
+ private fun setupKeepSettingsVisible() {
+ settings_keep_settings_visible.isChecked = config.keepSettingsVisible
+ settings_keep_settings_visible_holder.setOnClickListener {
+ settings_keep_settings_visible.toggle()
+ config.keepSettingsVisible = settings_keep_settings_visible.isChecked
+ }
+ }
+
+ private fun setupAlwaysOpenBackCamera() {
+ settings_always_open_back_camera.isChecked = config.alwaysOpenBackCamera
+ settings_always_open_back_camera_holder.setOnClickListener {
+ settings_always_open_back_camera.toggle()
+ config.alwaysOpenBackCamera = settings_always_open_back_camera.isChecked
+ }
+ }
+
+ private fun setupSavePhotoMetadata() {
+ settings_save_photo_metadata.isChecked = config.savePhotoMetadata
+ settings_save_photo_metadata_holder.setOnClickListener {
+ settings_save_photo_metadata.toggle()
+ config.savePhotoMetadata = settings_save_photo_metadata.isChecked
+ }
+ }
+
+ private fun setupSavePhotosFolder() {
+ settings_save_photos.text = getLastPart(config.savePhotosFolder)
+ settings_save_photos_holder.setOnClickListener {
+
+ /* FilePickerDialog(this, config.savePhotosFolder, false, showFAB = true) {
+ handleSAFDialog(it) {
+ config.savePhotosFolder = it
+ settings_save_photos.text = getLastPart(config.savePhotosFolder)
+ }
+ }*/
+ }
+ }
+
+ private fun setupPhotoQuality() {
+ updatePhotoQuality(config.photoQuality)
+ settings_photo_quality_holder.setOnClickListener {
+ val items = arrayListOf(
+ RadioItem(100, "100%"),
+ RadioItem(95, "95%"),
+ RadioItem(90, "90%"),
+ RadioItem(85, "85%"),
+ RadioItem(80, "80%"),
+ RadioItem(75, "75%"),
+ RadioItem(70, "70%"),
+ RadioItem(65, "65%"),
+ RadioItem(60, "60%"),
+ RadioItem(55, "55%"),
+ RadioItem(50, "50%"))
+
+ RadioGroupDialog(this@SettingsActivity, items, config.photoQuality) {
+ config.photoQuality = it as Int
+ updatePhotoQuality(it)
+ }
+ }
+ }
+
+ private fun updatePhotoQuality(quality: Int) {
+ settings_photo_quality.text = "$quality%"
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/SimpleActivity.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/SimpleActivity.kt
new file mode 100644
index 0000000..73b7757
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/SimpleActivity.kt
@@ -0,0 +1,30 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.activities
+
+import com.mindorks.waprofileimage.R
+import com.simplemobiletools.commons.activities.BaseSimpleActivity
+
+open class SimpleActivity : BaseSimpleActivity() {
+ override fun getAppIconIDs() = arrayListOf(
+ R.mipmap.ic_launcher_red,
+ R.mipmap.ic_launcher_pink,
+ R.mipmap.ic_launcher_purple,
+ R.mipmap.ic_launcher_deep_purple,
+ R.mipmap.ic_launcher_indigo,
+ R.mipmap.ic_launcher_blue,
+ R.mipmap.ic_launcher_light_blue,
+ R.mipmap.ic_launcher_cyan,
+ R.mipmap.ic_launcher_teal,
+ R.mipmap.ic_launcher_green,
+ R.mipmap.ic_launcher_light_green,
+ R.mipmap.ic_launcher_lime,
+ R.mipmap.ic_launcher_yellow,
+ R.mipmap.ic_launcher_amber,
+ R.mipmap.ic_launcher,
+ R.mipmap.ic_launcher_deep_orange,
+ R.mipmap.ic_launcher_brown,
+ R.mipmap.ic_launcher_blue_grey,
+ R.mipmap.ic_launcher_grey_black
+ )
+
+ override fun getAppLauncherName() = getString(R.string.app_launcher_name)
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/SplashActivity.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/SplashActivity.kt
new file mode 100644
index 0000000..f928344
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/activities/SplashActivity.kt
@@ -0,0 +1,16 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.activities
+
+import android.os.Bundle
+import android.app.Activity
+import com.mindorks.waprofileimage.R
+
+import kotlinx.android.synthetic.main.activity_splash.*
+
+class SplashActivity : Activity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_splash)
+ }
+
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/dialogs/ChangeResolutionDialog.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/dialogs/ChangeResolutionDialog.kt
new file mode 100644
index 0000000..398f5a6
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/dialogs/ChangeResolutionDialog.kt
@@ -0,0 +1,86 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.dialogs
+
+import android.app.AlertDialog
+import android.view.LayoutInflater
+import android.view.View
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.waprofileImage.newcam.activities.SimpleActivity
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.config
+import com.mindorks.waprofileimage.waprofileImage.newcam.models.MySize
+import com.simplemobiletools.commons.dialogs.RadioGroupDialog
+import com.simplemobiletools.commons.extensions.setupDialogStuff
+import com.simplemobiletools.commons.models.RadioItem
+import kotlinx.android.synthetic.main.dialog_change_resolution.view.*
+
+class ChangeResolutionDialog(val activity: SimpleActivity, val isFrontCamera: Boolean, val photoResolutions: ArrayList,
+ val videoResolutions: ArrayList, val openVideoResolutions: Boolean, val callback: () -> Unit) {
+ private val config = activity.config
+
+ init {
+ val view = LayoutInflater.from(activity).inflate(R.layout.dialog_change_resolution, null).apply {
+ setupPhotoResolutionPicker(this)
+ setupVideoResolutionPicker(this)
+ }
+
+ /* dialog = AlertDialog.Builder(activity)
+ .setPositiveButton(R.string.ok, null)
+ .setOnDismissListener { callback() }
+ .create().apply {
+ activity.setupDialogStuff(view, this, if (isFrontCamera) R.string.front_camera else R.string.back_camera) {
+ if (openVideoResolutions) {
+ view.change_resolution_video_holder.performClick()
+ }
+ }
+ }*/
+ }
+
+ private fun setupPhotoResolutionPicker(view: View) {
+ val items = getFormattedResolutions(photoResolutions)
+ var selectionIndex = if (isFrontCamera) config.frontPhotoResIndex else config.backPhotoResIndex
+ selectionIndex = Math.max(selectionIndex, 0)
+
+ view.change_resolution_photo_holder.setOnClickListener {
+ RadioGroupDialog(activity, items, selectionIndex) {
+ selectionIndex = it as Int
+ view.change_resolution_photo.text = items[selectionIndex].title
+ if (isFrontCamera) {
+ config.frontPhotoResIndex = it
+ } else {
+ config.backPhotoResIndex = it
+ }
+ // dialog.dismiss()
+ }
+ }
+ view.change_resolution_photo.text = items.getOrNull(selectionIndex)?.title
+ }
+
+ private fun setupVideoResolutionPicker(view: View) {
+ val items = getFormattedResolutions(videoResolutions)
+ var selectionIndex = if (isFrontCamera) config.frontVideoResIndex else config.backVideoResIndex
+
+ view.change_resolution_video_holder.setOnClickListener {
+ RadioGroupDialog(activity, items, selectionIndex) {
+ selectionIndex = it as Int
+ view.change_resolution_video.text = items[selectionIndex].title
+ if (isFrontCamera) {
+ config.frontVideoResIndex = it
+ } else {
+ config.backVideoResIndex = it
+ }
+ // dialog.dismiss()
+ }
+ }
+ view.change_resolution_video.text = items.getOrNull(selectionIndex)?.title
+ }
+
+ private fun getFormattedResolutions(resolutions: List): ArrayList {
+ val items = ArrayList(resolutions.size)
+ val sorted = resolutions.sortedByDescending { it.width * it.height }
+ sorted.forEachIndexed { index, size ->
+ val megapixels = String.format("%.1f", (size.width * size.height.toFloat()) / 1000000)
+ val aspectRatio = size.getAspectRatio(activity)
+ items.add(RadioItem(index, "${size.width} x ${size.height} ($megapixels MP, $aspectRatio)"))
+ }
+ return items
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/extensions/Context.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/extensions/Context.kt
new file mode 100644
index 0000000..97633fa
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/extensions/Context.kt
@@ -0,0 +1,51 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.extensions
+
+import android.content.Context
+import android.graphics.Point
+import android.view.WindowManager
+import com.mindorks.waprofileimage.waprofileImage.newcam.helpers.Config
+import com.mindorks.waprofileimage.waprofileImage.newcam.implementations.MyCameraOneImpl
+import com.mindorks.waprofileimage.waprofileImage.newcam.implementations.MyCameraTwoImpl
+import com.simplemobiletools.commons.helpers.isLollipopPlus
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.*
+
+val Context.config: Config get() = Config.newInstance(applicationContext)
+
+internal val Context.windowManager: WindowManager get() = getSystemService(Context.WINDOW_SERVICE) as WindowManager
+
+fun Context.getOutputMediaFile(isPhoto: Boolean): String {
+ val mediaStorageDir = File(config.savePhotosFolder)
+
+ if (!mediaStorageDir.exists()) {
+ if (!mediaStorageDir.mkdirs()) {
+ return ""
+ }
+ }
+
+ val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
+ return if (isPhoto) {
+ "${mediaStorageDir.path}/IMG_$timestamp.jpg"
+ } else {
+ "${mediaStorageDir.path}/VID_$timestamp.mp4"
+ }
+}
+
+val Context.usableScreenSize: Point
+ get() {
+ val size = Point()
+ windowManager.defaultDisplay.getSize(size)
+ return size
+ }
+
+val Context.realScreenSize: Point
+ get() {
+ val size = Point()
+ windowManager.defaultDisplay.getRealSize(size)
+ return size
+ }
+
+val Context.navBarHeight: Int get() = realScreenSize.y - usableScreenSize.y
+
+fun Context.getMyCamera() = if (isLollipopPlus()) MyCameraTwoImpl(applicationContext) else MyCameraOneImpl(applicationContext)
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/helpers/Config.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/helpers/Config.kt
new file mode 100644
index 0000000..b5d3101
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/helpers/Config.kt
@@ -0,0 +1,95 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.helpers
+
+import android.content.Context
+import android.os.Environment
+import com.simplemobiletools.commons.helpers.BaseConfig
+import java.io.File
+
+class Config(context: Context) : BaseConfig(context) {
+ companion object {
+ fun newInstance(context: Context) = Config(context)
+ }
+
+ var savePhotosFolder: String
+ get(): String {
+ var path = prefs.getString(SAVE_PHOTOS, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString())
+ if (!File(path).exists() || !File(path).isDirectory) {
+ path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString()
+ savePhotosFolder = path
+ }
+ return path
+ }
+ set(path) = prefs.edit().putString(SAVE_PHOTOS, path).apply()
+
+ var isShowPreviewEnabled: Boolean
+ get() = prefs.getBoolean(SHOW_PREVIEW, false)
+ set(enabled) = prefs.edit().putBoolean(SHOW_PREVIEW, enabled).apply()
+
+ var isSoundEnabled: Boolean
+ get() = prefs.getBoolean(SOUND, true)
+ set(enabled) = prefs.edit().putBoolean(SOUND, enabled).apply()
+
+ var focusBeforeCapture: Boolean
+ get() = prefs.getBoolean(FOCUS_BEFORE_CAPTURE, false)
+ set(focus) = prefs.edit().putBoolean(FOCUS_BEFORE_CAPTURE, focus).apply()
+
+ var volumeButtonsAsShutter: Boolean
+ get() = prefs.getBoolean(VOLUME_BUTTONS_AS_SHUTTER, false)
+ set(volumeButtonsAsShutter) = prefs.edit().putBoolean(VOLUME_BUTTONS_AS_SHUTTER, volumeButtonsAsShutter).apply()
+
+ var turnFlashOffAtStartup: Boolean
+ get() = prefs.getBoolean(TURN_FLASH_OFF_AT_STARTUP, false)
+ set(turnFlashOffAtStartup) = prefs.edit().putBoolean(TURN_FLASH_OFF_AT_STARTUP, turnFlashOffAtStartup).apply()
+
+ var flipPhotos: Boolean
+ get() = prefs.getBoolean(FLIP_PHOTOS, true)
+ set(flipPhotos) = prefs.edit().putBoolean(FLIP_PHOTOS, flipPhotos).apply()
+
+ var lastUsedCamera: String
+ get() = prefs.getString(LAST_USED_CAMERA, "0")
+ set(cameraId) = prefs.edit().putString(LAST_USED_CAMERA, cameraId).apply()
+
+ var initPhotoMode: Boolean
+ get() = prefs.getBoolean(INIT_PHOTO_MODE, true)
+ set(initPhotoMode) = prefs.edit().putBoolean(INIT_PHOTO_MODE, initPhotoMode).apply()
+
+ var flashlightState: Int
+ get() = prefs.getInt(FLASHLIGHT_STATE, FLASH_OFF)
+ set(state) = prefs.edit().putInt(FLASHLIGHT_STATE, state).apply()
+
+ var backPhotoResIndex: Int
+ get() = prefs.getInt(BACK_PHOTO_RESOLUTION_INDEX, 0)
+ set(backPhotoResIndex) = prefs.edit().putInt(BACK_PHOTO_RESOLUTION_INDEX, backPhotoResIndex).apply()
+
+ var backVideoResIndex: Int
+ get() = prefs.getInt(BACK_VIDEO_RESOLUTION_INDEX, 0)
+ set(backVideoResIndex) = prefs.edit().putInt(BACK_VIDEO_RESOLUTION_INDEX, backVideoResIndex).apply()
+
+ var frontPhotoResIndex: Int
+ get() = prefs.getInt(FRONT_PHOTO_RESOLUTION_INDEX, 0)
+ set(frontPhotoResIndex) = prefs.edit().putInt(FRONT_PHOTO_RESOLUTION_INDEX, frontPhotoResIndex).apply()
+
+ var frontVideoResIndex: Int
+ get() = prefs.getInt(FRONT_VIDEO_RESOLUTION_INDEX, 0)
+ set(frontVideoResIndex) = prefs.edit().putInt(FRONT_VIDEO_RESOLUTION_INDEX, frontVideoResIndex).apply()
+
+ var wasPhotoPreviewHintShown: Boolean
+ get() = prefs.getBoolean(PHOTO_PREVIEW_HINT_SHOWN, false)
+ set(wasPhotoPreviewHintShown) = prefs.edit().putBoolean(PHOTO_PREVIEW_HINT_SHOWN, wasPhotoPreviewHintShown).apply()
+
+ var keepSettingsVisible: Boolean
+ get() = prefs.getBoolean(KEEP_SETTINGS_VISIBLE, false)
+ set(keepSettingsVisible) = prefs.edit().putBoolean(KEEP_SETTINGS_VISIBLE, keepSettingsVisible).apply()
+
+ var alwaysOpenBackCamera: Boolean
+ get() = prefs.getBoolean(ALWAYS_OPEN_BACK_CAMERA, false)
+ set(alwaysOpenBackCamera) = prefs.edit().putBoolean(ALWAYS_OPEN_BACK_CAMERA, alwaysOpenBackCamera).apply()
+
+ var savePhotoMetadata: Boolean
+ get() = prefs.getBoolean(SAVE_PHOTO_METADATA, true)
+ set(savePhotoMetadata) = prefs.edit().putBoolean(SAVE_PHOTO_METADATA, savePhotoMetadata).apply()
+
+ var photoQuality: Int
+ get() = prefs.getInt(PHOTO_QUALITY, 80)
+ set(photoQuality) = prefs.edit().putInt(PHOTO_QUALITY, photoQuality).apply()
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/helpers/Constants.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/helpers/Constants.kt
new file mode 100644
index 0000000..d542256
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/helpers/Constants.kt
@@ -0,0 +1,48 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.helpers
+
+const val ORIENT_PORTRAIT = 0
+const val ORIENT_LANDSCAPE_LEFT = 1
+const val ORIENT_LANDSCAPE_RIGHT = 2
+
+// shared preferences
+const val SAVE_PHOTOS = "save_photos"
+const val SHOW_PREVIEW = "show_preview"
+const val SOUND = "sound"
+const val FOCUS_BEFORE_CAPTURE = "focus_before_capture_2"
+const val VOLUME_BUTTONS_AS_SHUTTER = "volume_buttons_as_shutter"
+const val TURN_FLASH_OFF_AT_STARTUP = "turn_flash_off_at_startup"
+const val FLIP_PHOTOS = "flip_photos"
+const val LAST_USED_CAMERA = "last_used_camera_2"
+const val FLASHLIGHT_STATE = "flashlight_state"
+const val INIT_PHOTO_MODE = "init_photo_mode"
+const val BACK_PHOTO_RESOLUTION_INDEX = "back_photo_resolution_index_2"
+const val BACK_VIDEO_RESOLUTION_INDEX = "back_video_resolution_index_2"
+const val FRONT_PHOTO_RESOLUTION_INDEX = "front_photo_resolution_index_2"
+const val FRONT_VIDEO_RESOLUTION_INDEX = "front_video_resolution_index_2"
+const val PHOTO_PREVIEW_HINT_SHOWN = "photo_preview_hint_shown"
+const val KEEP_SETTINGS_VISIBLE = "keep_settings_visible"
+const val ALWAYS_OPEN_BACK_CAMERA = "always_open_back_camera"
+const val SAVE_PHOTO_METADATA = "save_photo_metadata"
+const val PHOTO_QUALITY = "photo_quality"
+
+const val FLASH_OFF = 0
+const val FLASH_ON = 1
+const val FLASH_AUTO = 2
+
+// camera states
+const val STATE_INIT = 0
+const val STATE_PREVIEW = 1
+const val STATE_PICTURE_TAKEN = 2
+const val STATE_WAITING_LOCK = 3
+const val STATE_WAITING_PRECAPTURE = 4
+const val STATE_WAITING_NON_PRECAPTURE = 5
+const val STATE_STARTING_RECORDING = 6
+const val STATE_STOPING_RECORDING = 7
+const val STATE_RECORDING = 8
+
+fun compensateDeviceRotation(orientation: Int, isUsingFrontCamera: Boolean) = when {
+ orientation == ORIENT_LANDSCAPE_LEFT -> 270
+ orientation == ORIENT_LANDSCAPE_RIGHT -> 90
+ isUsingFrontCamera -> 180
+ else -> 0
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/helpers/PhotoProcessor.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/helpers/PhotoProcessor.kt
new file mode 100644
index 0000000..acfc018
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/helpers/PhotoProcessor.kt
@@ -0,0 +1,163 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.helpers
+
+import android.annotation.SuppressLint
+import android.graphics.Bitmap
+import android.graphics.BitmapFactory
+import android.graphics.Matrix
+import android.media.ExifInterface
+import android.net.Uri
+import android.os.AsyncTask
+import android.os.Environment
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.waprofileImage.newcam.activities.MainActivity
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.config
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.getOutputMediaFile
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.isNougatPlus
+import java.io.File
+import java.io.FileNotFoundException
+import java.io.FileOutputStream
+import java.io.OutputStream
+
+class PhotoProcessor(val activity: MainActivity, val saveUri: Uri?, val deviceOrientation: Int, val previewRotation: Int, val isUsingFrontCamera: Boolean,
+ val isThirdPartyIntent: Boolean) :
+ AsyncTask() {
+
+ @SuppressLint("NewApi")
+ override fun doInBackground(vararg params: ByteArray): String {
+ var fos: OutputStream? = null
+ val path: String
+ try {
+ path = if (saveUri != null) {
+ saveUri.path
+ } else {
+ activity.getOutputMediaFile(true)
+ }
+
+ if (path.isEmpty()) {
+ return ""
+ }
+
+ val data = params[0]
+ val tempFile = File.createTempFile("simple_temp_exif", "")
+ val tempFOS = FileOutputStream(tempFile)
+ tempFOS.use {
+ tempFOS.write(data)
+ }
+ val tempExif = ExifInterface(tempFile.absolutePath)
+
+ val photoFile = File(path)
+ if (activity.needsStupidWritePermissions(path)) {
+ if (!activity.hasProperStoredTreeUri()) {
+ activity.toast(R.string.save_error_internal_storage)
+ activity.config.savePhotosFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString()
+ return ""
+ }
+
+ var document = activity.getDocumentFile(path.getParentPath())
+ document = document?.createFile("", path.substring(path.lastIndexOf('/') + 1))
+ if (document == null) {
+ activity.toast(R.string.save_error_internal_storage)
+ return ""
+ }
+
+ fos = activity.contentResolver.openOutputStream(document.uri)
+ } else {
+ fos = if (saveUri == null) {
+ FileOutputStream(photoFile)
+ } else {
+ activity.contentResolver.openOutputStream(saveUri)
+ }
+ }
+
+ val exif = try {
+ ExifInterface(path)
+ } catch (e: Exception) {
+ null
+ }
+
+ val orient = exif?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)
+ ?: ExifInterface.ORIENTATION_UNDEFINED
+
+ val imageRot = orient.degreesFromOrientation()
+
+ val deviceRot = compensateDeviceRotation(deviceOrientation, isUsingFrontCamera)
+ var image = BitmapFactory.decodeByteArray(data, 0, data.size)
+ val totalRotation = (imageRot + deviceRot + previewRotation) % 360
+
+ if (path.startsWith(activity.internalStoragePath) || isNougatPlus() && !isThirdPartyIntent) {
+ // do not rotate the image itself in these cases, rotate it by exif only
+ } else {
+ // make sure the image itself is rotated at third party intents
+ image = rotate(image, totalRotation)
+ }
+
+ if (isUsingFrontCamera && activity.config.flipPhotos) {
+ val matrix = Matrix()
+ if (path.startsWith(activity.internalStoragePath)) {
+ matrix.preScale(1f, -1f)
+ } else {
+ matrix.preScale(-1f, 1f)
+ }
+
+ try {
+ image = Bitmap.createBitmap(image, 0, 0, image.width, image.height, matrix, false)
+ } catch (e: OutOfMemoryError) {
+ activity.toast(R.string.out_of_memory_error)
+ }
+ }
+
+ try {
+ image.compress(Bitmap.CompressFormat.JPEG, activity.config.photoQuality, fos)
+ if (!isThirdPartyIntent) {
+ activity.saveImageRotation(path, totalRotation)
+ }
+ } catch (e: Exception) {
+ activity.showErrorToast(e)
+ return ""
+ }
+
+ if (activity.config.savePhotoMetadata && !isThirdPartyIntent) {
+ val fileExif = ExifInterface(path)
+ tempExif.copyTo(fileExif)
+ }
+
+ return photoFile.absolutePath
+ } catch (e: FileNotFoundException) {
+ } finally {
+ fos?.close()
+ }
+
+ return ""
+ }
+
+ private fun rotate(bitmap: Bitmap, degree: Int): Bitmap? {
+ if (degree == 0) {
+ return bitmap
+ }
+
+ val width = bitmap.width
+ val height = bitmap.height
+
+ val matrix = Matrix()
+ matrix.setRotate(degree.toFloat())
+
+ try {
+ return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true)
+ } catch (e: OutOfMemoryError) {
+ activity.showErrorToast(e.toString())
+ }
+ return null
+ }
+
+ override fun onPostExecute(path: String) {
+ super.onPostExecute(path)
+ if (path.isNotEmpty()) {
+ activity.mediaSaved(path)
+ }
+ }
+
+ interface MediaSavedListener {
+ fun mediaSaved(path: String)
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/implementations/MyCameraOneImpl.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/implementations/MyCameraOneImpl.kt
new file mode 100644
index 0000000..8be70d3
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/implementations/MyCameraOneImpl.kt
@@ -0,0 +1,14 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.implementations
+
+import android.content.Context
+import android.hardware.Camera
+import com.mindorks.waprofileimage.waprofileImage.newcam.interfaces.MyCamera
+
+@Suppress("DEPRECATION")
+class MyCameraOneImpl(val context: Context) : MyCamera() {
+ override fun getFrontCameraId() = Camera.CameraInfo.CAMERA_FACING_FRONT
+
+ override fun getBackCameraId() = Camera.CameraInfo.CAMERA_FACING_BACK
+
+ override fun getCountOfCameras() = Camera.getNumberOfCameras()
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/implementations/MyCameraTwoImpl.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/implementations/MyCameraTwoImpl.kt
new file mode 100644
index 0000000..170fe90
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/implementations/MyCameraTwoImpl.kt
@@ -0,0 +1,20 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.implementations
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.os.Build
+import com.mindorks.waprofileimage.waprofileImage.newcam.interfaces.MyCamera
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+class MyCameraTwoImpl(val context: Context) : MyCamera() {
+ override fun getFrontCameraId() = CameraCharacteristics.LENS_FACING_FRONT
+
+ override fun getBackCameraId() = CameraCharacteristics.LENS_FACING_BACK
+
+ override fun getCountOfCameras(): Int {
+ val manager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+ return manager.cameraIdList.size
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/interfaces/MyCamera.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/interfaces/MyCamera.kt
new file mode 100644
index 0000000..a53c8f1
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/interfaces/MyCamera.kt
@@ -0,0 +1,9 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.interfaces
+
+abstract class MyCamera {
+ abstract fun getFrontCameraId(): Int
+
+ abstract fun getBackCameraId(): Int
+
+ abstract fun getCountOfCameras(): Int
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/interfaces/MyPreview.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/interfaces/MyPreview.kt
new file mode 100644
index 0000000..690577e
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/interfaces/MyPreview.kt
@@ -0,0 +1,41 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.interfaces
+
+import android.net.Uri
+
+interface MyPreview {
+ fun onResumed()
+
+ fun onPaused()
+
+ fun setTargetUri(uri: Uri)
+
+ fun setIsImageCaptureIntent(isImageCaptureIntent: Boolean)
+
+ fun setFlashlightState(state: Int)
+
+ fun getCameraState(): Int
+
+ fun showChangeResolutionDialog()
+
+ fun toggleFrontBackCamera()
+
+ fun toggleFlashlight()
+
+ fun tryTakePicture()
+
+ fun toggleRecording()
+
+ fun tryInitVideoMode()
+
+ fun initPhotoMode()
+
+ fun initVideoMode(): Boolean
+
+ fun checkFlashlight()
+
+ fun deviceOrientationChanged()
+
+ fun resumeCamera(): Boolean
+
+ fun imageSaved()
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/models/FocusArea.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/models/FocusArea.kt
new file mode 100644
index 0000000..176e18f
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/models/FocusArea.kt
@@ -0,0 +1,5 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.models
+
+import android.graphics.Rect
+
+data class FocusArea(val rect: Rect, val weight: Int)
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/models/MySize.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/models/MySize.kt
new file mode 100644
index 0000000..484fd9b
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/models/MySize.kt
@@ -0,0 +1,50 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.models
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.os.Build
+import android.util.Size
+import com.mindorks.waprofileimage.R
+
+data class MySize(val width: Int, val height: Int) {
+ val ratio = width / height.toFloat()
+ fun isSixteenToNine() = ratio == 16 / 9f
+
+ private fun isFiveToThree() = ratio == 5 / 3f
+
+ private fun isFourToThree() = ratio == 4 / 3f
+
+ private fun isTwoToOne() = ratio == 2f
+
+ private fun isThreeToFour() = ratio == 3 / 4f
+
+ private fun isThreeToTwo() = ratio == 3 / 2f
+
+ private fun isSixToFive() = ratio == 6 / 5f
+
+ private fun isNineteenToNine() = ratio == 19 / 9f
+
+ private fun isNineteenToEight() = ratio == 19 / 8f
+
+ private fun isOneNineToOne() = ratio == 1.9f
+
+ private fun isSquare() = width == height
+
+ fun getAspectRatio(context: Context) = when {
+ isSixteenToNine() -> "16:9"
+ isFiveToThree() -> "5:3"
+ isFourToThree() -> "4:3"
+ isThreeToFour() -> "3:4"
+ isThreeToTwo() -> "3:2"
+ isSixToFive() -> "6:5"
+ isOneNineToOne() -> "1.9:1"
+ isNineteenToNine() -> "19:9"
+ isNineteenToEight() -> "19:8"
+ isSquare() -> "1:1"
+ isTwoToOne() -> "2:1"
+ else -> context.resources.getString(R.string.other)
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ fun toSize() = Size(width, height)
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/receivers/HardwareShutterReceiver.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/receivers/HardwareShutterReceiver.kt
new file mode 100644
index 0000000..fbbf819
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/receivers/HardwareShutterReceiver.kt
@@ -0,0 +1,16 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.receivers
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import com.mindorks.waprofileimage.waprofileImage.newcam.activities.MainActivity
+
+class HardwareShutterReceiver : BroadcastReceiver() {
+
+ override fun onReceive(context: Context, intent: Intent) {
+ Intent(context.applicationContext, MainActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
+ context.startActivity(this)
+ }
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/AutoFitTextureView.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/AutoFitTextureView.kt
new file mode 100644
index 0000000..4e0e838
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/AutoFitTextureView.kt
@@ -0,0 +1,39 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.views
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.TextureView
+import android.view.View
+
+// taken from the official Camera2 sample at https://github.com/googlesamples/android-Camera2Basic
+class AutoFitTextureView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : TextureView(context, attrs, defStyle) {
+
+ private var mRatioWidth = 0
+ private var mRatioHeight = 0
+
+ fun setAspectRatio(width: Int, height: Int) {
+ if (width < 0 || height < 0) {
+ throw IllegalArgumentException("Size cannot be negative.")
+ }
+
+ mRatioWidth = width
+ mRatioHeight = height
+ requestLayout()
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ val width = View.MeasureSpec.getSize(widthMeasureSpec)
+ val height = View.MeasureSpec.getSize(heightMeasureSpec)
+
+ if (mRatioWidth == 0 || mRatioHeight == 0) {
+ setMeasuredDimension(width, height)
+ } else {
+ if (width < height * mRatioWidth / mRatioHeight) {
+ setMeasuredDimension(width, width * mRatioHeight / mRatioWidth)
+ } else {
+ setMeasuredDimension(height * mRatioWidth / mRatioHeight, height)
+ }
+ }
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/FocusCircleView.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/FocusCircleView.kt
new file mode 100644
index 0000000..f200e66
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/FocusCircleView.kt
@@ -0,0 +1,58 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.views
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.os.Handler
+import android.view.ViewGroup
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.config
+
+class FocusCircleView(context: Context) : ViewGroup(context) {
+ private val CIRCLE_RADIUS = 50f
+ private val CIRCLE_DURATION = 500L
+
+ private var mDrawCircle = false
+ private var mHandler: Handler
+ private var mPaint: Paint
+ private var mLastCenterX = 0f
+ private var mLastCenterY = 0f
+
+ init {
+ setWillNotDraw(false)
+ mHandler = Handler()
+ mPaint = Paint().apply {
+ style = Paint.Style.STROKE
+ color = context.config.primaryColor
+ strokeWidth = 2f
+ }
+ }
+
+ fun setStrokeColor(color: Int) {
+ mPaint.color = color
+ }
+
+ fun drawFocusCircle(x: Float, y: Float) {
+ mLastCenterX = x
+ mLastCenterY = y
+ toggleCircle(true)
+
+ mHandler.removeCallbacksAndMessages(null)
+ mHandler.postDelayed({
+ toggleCircle(false)
+ }, CIRCLE_DURATION)
+ }
+
+ private fun toggleCircle(show: Boolean) {
+ mDrawCircle = show
+ invalidate()
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+ if (mDrawCircle) {
+ canvas.drawCircle(mLastCenterX, mLastCenterY, CIRCLE_RADIUS, mPaint)
+ }
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/PreviewCameraOne.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/PreviewCameraOne.kt
new file mode 100644
index 0000000..1e4a7a6
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/PreviewCameraOne.kt
@@ -0,0 +1,922 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.views
+
+import android.annotation.SuppressLint
+import android.annotation.TargetApi
+import android.app.Activity
+import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.hardware.Camera
+import android.media.AudioManager
+import android.media.CamcorderProfile
+import android.media.MediaPlayer
+import android.media.MediaRecorder
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.os.Handler
+import android.view.*
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.waprofileImage.newcam.activities.MainActivity
+import com.mindorks.waprofileimage.waprofileImage.newcam.dialogs.ChangeResolutionDialog
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.config
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.getMyCamera
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.getOutputMediaFile
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.realScreenSize
+import com.mindorks.waprofileimage.waprofileImage.newcam.helpers.*
+import com.mindorks.waprofileimage.waprofileImage.newcam.implementations.MyCameraOneImpl
+import com.mindorks.waprofileimage.waprofileImage.newcam.interfaces.MyPreview
+import com.mindorks.waprofileimage.waprofileImage.newcam.models.MySize
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.isJellyBean1Plus
+import java.io.File
+import java.io.IOException
+import java.util.*
+
+class PreviewCameraOne : ViewGroup, SurfaceHolder.Callback, MyPreview {
+ private val FOCUS_AREA_SIZE = 100
+ private val PHOTO_PREVIEW_LENGTH = 500L
+ private val REFOCUS_PERIOD = 3000L
+
+ private lateinit var mSurfaceHolder: SurfaceHolder
+ private lateinit var mSurfaceView: SurfaceView
+ private lateinit var mScreenSize: Point
+ private lateinit var mConfig: Config
+ private var mSupportedPreviewSizes: List? = null
+ private var mPreviewSize: Camera.Size? = null
+ private var mParameters: Camera.Parameters? = null
+ private var mRecorder: MediaRecorder? = null
+ private var mScaleGestureDetector: ScaleGestureDetector? = null
+ private var mZoomRatios = ArrayList()
+ private var mFlashlightState = FLASH_OFF
+ private var mCamera: Camera? = null
+ private var mCameraImpl: MyCameraOneImpl? = null
+ private var mAutoFocusHandler = Handler()
+ private var mActivity: MainActivity? = null
+ private var mTargetUri: Uri? = null
+ private var mCameraState = STATE_PREVIEW
+
+ private var mCurrVideoPath = ""
+ private var mCanTakePicture = false
+ private var mIsRecording = false
+ private var mIsInVideoMode = false
+ private var mIsSurfaceCreated = false
+ private var mSwitchToVideoAsap = false
+ private var mSetupPreviewAfterMeasure = false
+ private var mIsSixteenToNine = false
+ private var mWasZooming = false
+ private var mIsPreviewShown = false
+ private var mWasCameraPreviewSet = false
+ private var mIsImageCaptureIntent = false
+ private var mIsFocusingBeforeCapture = false
+ private var mLastClickX = 0f
+ private var mLastClickY = 0f
+ private var mCurrCameraId = 0
+ private var mMaxZoom = 0
+ private var mRotationAtCapture = 0
+
+ constructor(context: Context) : super(context)
+
+ @SuppressLint("ClickableViewAccessibility")
+ constructor(activity: MainActivity, surfaceView: SurfaceView) : super(activity) {
+ mActivity = activity
+ mSurfaceView = surfaceView
+ mSurfaceHolder = mSurfaceView.holder
+ mSurfaceHolder.addCallback(this)
+ mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
+ mCameraImpl = MyCameraOneImpl(activity.applicationContext)
+ mConfig = activity.config
+ mScreenSize = getScreenSize()
+ initGestureDetector()
+
+ mSurfaceView.setOnTouchListener { view, event ->
+ mLastClickX = event.x
+ mLastClickY = event.y
+
+ if (mMaxZoom > 0 && mParameters?.isZoomSupported == true) {
+ mScaleGestureDetector!!.onTouchEvent(event)
+ }
+ false
+ }
+
+ mSurfaceView.setOnClickListener {
+ if (mIsPreviewShown) {
+ resumePreview()
+ } else {
+ if (!mWasZooming && !mIsPreviewShown) {
+ focusArea(false)
+ }
+
+ mWasZooming = false
+ mSurfaceView.isSoundEffectsEnabled = true
+ }
+ }
+ }
+
+ override fun onResumed() {}
+
+ override fun onPaused() {
+ releaseCamera()
+ }
+
+ override fun tryInitVideoMode() {
+ if (mIsSurfaceCreated) {
+ initVideoMode()
+ } else {
+ mSwitchToVideoAsap = true
+ }
+ }
+
+ override fun resumeCamera(): Boolean {
+ val newCamera: Camera
+ try {
+ newCamera = Camera.open(mCurrCameraId)
+ mActivity!!.setIsCameraAvailable(true)
+ } catch (e: Exception) {
+ mActivity!!.showErrorToast(e)
+ mActivity!!.setIsCameraAvailable(false)
+ return false
+ }
+
+ if (mCamera === newCamera) {
+ return false
+ }
+
+ releaseCamera()
+ mCamera = newCamera
+ if (initCamera() && mIsInVideoMode) {
+ initVideoMode()
+ }
+
+ if (!mWasCameraPreviewSet && mIsSurfaceCreated) {
+ mCamera!!.setPreviewDisplay(mSurfaceHolder)
+ mWasCameraPreviewSet = true
+ }
+ return true
+ }
+
+ override fun imageSaved() {}
+
+ private fun initCamera(): Boolean {
+ if (mCamera == null)
+ return false
+
+ mParameters = mCamera!!.parameters
+ mMaxZoom = mParameters!!.maxZoom
+
+ if (mParameters!!.isZoomSupported)
+ mZoomRatios = mParameters!!.zoomRatios as ArrayList
+
+ mSupportedPreviewSizes = mParameters!!.supportedPreviewSizes.sortedByDescending { it.width * it.height }
+ refreshPreview()
+
+ // hackfix for slow photo preview, more info at https://github.com/SimpleMobileTools/Simple-Camera/issues/120
+ if (Build.MODEL == "Nexus 4") {
+ mParameters!!.setRecordingHint(true)
+ }
+
+ val focusModes = mParameters!!.supportedFocusModes
+ if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+ mParameters!!.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
+ }
+
+ mCamera!!.setDisplayOrientation(getPreviewRotation(mCurrCameraId))
+ mParameters!!.zoom = 0
+ updateCameraParameters()
+
+ if (mCanTakePicture) {
+ try {
+ mCamera!!.setPreviewDisplay(mSurfaceHolder)
+ } catch (e: IOException) {
+ mActivity!!.showErrorToast(e)
+ return false
+ }
+ }
+
+ mActivity!!.setFlashAvailable(hasFlash(mCamera))
+ return true
+ }
+
+ override fun toggleFrontBackCamera() {
+ mCurrCameraId = if (mCurrCameraId == mCameraImpl!!.getBackCameraId()) {
+ mCameraImpl!!.getFrontCameraId()
+ } else {
+ mCameraImpl!!.getBackCameraId()
+ }
+
+ mConfig.lastUsedCamera = mCurrCameraId.toString()
+ releaseCamera()
+ if (resumeCamera()) {
+ setFlashlightState(FLASH_OFF)
+ mActivity?.updateCameraIcon(mCurrCameraId == mCameraImpl!!.getFrontCameraId())
+ mActivity?.toggleTimer(false)
+ } else {
+ mActivity?.toast(R.string.camera_switch_error)
+ }
+ }
+
+ override fun getCameraState() = mCameraState
+
+ private fun refreshPreview() {
+ mIsSixteenToNine = getSelectedResolution().isSixteenToNine()
+ mSetupPreviewAfterMeasure = true
+ requestLayout()
+ invalidate()
+ rescheduleAutofocus()
+ }
+
+ private fun getSelectedResolution(): MySize {
+ if (mParameters == null) {
+ mParameters = mCamera!!.parameters
+ }
+
+ var index = getResolutionIndex()
+ val resolutions = if (mIsInVideoMode) {
+ mParameters!!.supportedVideoSizes ?: mParameters!!.supportedPreviewSizes
+ } else {
+ mParameters!!.supportedPictureSizes
+ }.map { MySize(it.width, it.height) }.sortedByDescending { it.width * it.height }
+
+ if (index == -1) {
+ index = getDefaultFullscreenResolution(resolutions) ?: 0
+ }
+
+ return resolutions[index]
+ }
+
+ private fun getResolutionIndex(): Int {
+ val isBackCamera = mConfig.lastUsedCamera == Camera.CameraInfo.CAMERA_FACING_BACK.toString()
+ return if (mIsInVideoMode) {
+ if (isBackCamera) mConfig.backVideoResIndex else mConfig.frontVideoResIndex
+ } else {
+ if (isBackCamera) mConfig.backPhotoResIndex else mConfig.frontPhotoResIndex
+ }
+ }
+
+ private fun getDefaultFullscreenResolution(resolutions: List): Int? {
+ val screenAspectRatio = mActivity!!.realScreenSize.y / mActivity!!.realScreenSize.x.toFloat()
+ resolutions.forEachIndexed { index, size ->
+ val diff = screenAspectRatio - (size.width / size.height.toFloat())
+ if (Math.abs(diff) < 0.1f) {
+ mConfig.backPhotoResIndex = index
+ return index
+ }
+ }
+ return null
+ }
+
+ private fun initGestureDetector() {
+ mScaleGestureDetector = ScaleGestureDetector(mActivity, object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
+ override fun onScale(detector: ScaleGestureDetector): Boolean {
+ val zoomFactor = mParameters!!.zoom
+ var zoomRatio = mZoomRatios[zoomFactor] / 100f
+ zoomRatio *= detector.scaleFactor
+
+ var newZoomFactor = zoomFactor
+ if (zoomRatio <= 1f) {
+ newZoomFactor = 0
+ } else if (zoomRatio >= mZoomRatios[mMaxZoom] / 100f) {
+ newZoomFactor = mMaxZoom
+ } else {
+ if (detector.scaleFactor > 1f) {
+ for (i in zoomFactor until mZoomRatios.size) {
+ if (mZoomRatios[i] / 100.0f >= zoomRatio) {
+ newZoomFactor = i
+ break
+ }
+ }
+ } else {
+ for (i in zoomFactor downTo 0) {
+ if (mZoomRatios[i] / 100.0f <= zoomRatio) {
+ newZoomFactor = i
+ break
+ }
+ }
+ }
+ }
+
+ newZoomFactor = Math.max(newZoomFactor, 0)
+ newZoomFactor = Math.min(mMaxZoom, newZoomFactor)
+
+ mParameters!!.zoom = newZoomFactor
+ updateCameraParameters()
+ return true
+ }
+
+ override fun onScaleEnd(detector: ScaleGestureDetector) {
+ super.onScaleEnd(detector)
+ mWasZooming = true
+ mSurfaceView.isSoundEffectsEnabled = false
+ mParameters!!.focusAreas = null
+ }
+ })
+ }
+
+ override fun tryTakePicture() {
+ if (mConfig.focusBeforeCapture) {
+ focusArea(true)
+ } else {
+ takePicture()
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ private fun takePicture() {
+ if (mCanTakePicture) {
+ val selectedResolution = getSelectedResolution()
+ mParameters!!.setPictureSize(selectedResolution.width, selectedResolution.height)
+ val pictureSize = mParameters!!.pictureSize
+ if (selectedResolution.width != pictureSize.width || selectedResolution.height != pictureSize.height) {
+ mActivity!!.toast(R.string.setting_resolution_failed)
+ }
+
+ if (isJellyBean1Plus()) {
+ mCamera!!.enableShutterSound(false)
+ }
+
+ mRotationAtCapture = mActivity!!.mLastHandledOrientation
+ updateCameraParameters()
+ mCameraState = STATE_PICTURE_TAKEN
+ mIsPreviewShown = true
+ try {
+ Thread {
+ mCamera!!.takePicture(null, null, takePictureCallback)
+
+ if (mConfig.isSoundEnabled) {
+ val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
+ val volume = audioManager.getStreamVolume(AudioManager.STREAM_SYSTEM)
+ if (volume != 0) {
+ val mp = MediaPlayer.create(context, Uri.parse("file:///system/media/audio/ui/camera_click.ogg"))
+ mp?.start()
+ }
+ }
+ }.start()
+ } catch (ignored: Exception) {
+ }
+ }
+ mCanTakePicture = false
+ mIsFocusingBeforeCapture = false
+ }
+
+ private val takePictureCallback = Camera.PictureCallback { data, cam ->
+ if (data.isEmpty()) {
+ mActivity!!.toast(R.string.unknown_error_occurred)
+ return@PictureCallback
+ }
+
+ mCameraState = STATE_PREVIEW
+ if (!mIsImageCaptureIntent) {
+ handlePreview()
+ }
+
+ if (mIsImageCaptureIntent) {
+ if (mTargetUri != null) {
+ storePhoto(data)
+ } else {
+ mActivity!!.apply {
+ setResult(Activity.RESULT_OK)
+ finish()
+ }
+ }
+ } else {
+ storePhoto(data)
+ }
+ }
+
+ private fun storePhoto(data: ByteArray) {
+ val previewRotation = getPreviewRotation(mCurrCameraId)
+ PhotoProcessor(mActivity!!, mTargetUri, mRotationAtCapture, previewRotation, getIsUsingFrontCamera(), mIsImageCaptureIntent).execute(data)
+ }
+
+ private fun getIsUsingFrontCamera() = mCurrCameraId == mActivity!!.getMyCamera().getFrontCameraId()
+
+ private fun handlePreview() {
+ if (mConfig.isShowPreviewEnabled) {
+ if (!mConfig.wasPhotoPreviewHintShown) {
+ mActivity!!.toast(R.string.click_to_resume_preview)
+ mConfig.wasPhotoPreviewHintShown = true
+ }
+ } else {
+ Handler().postDelayed({
+ mIsPreviewShown = false
+ resumePreview()
+ }, PHOTO_PREVIEW_LENGTH)
+ }
+ }
+
+ private fun resumePreview() {
+ mIsPreviewShown = false
+ mActivity!!.toggleBottomButtons(false)
+ try {
+ mCamera?.startPreview()
+ } catch (ignored: Exception) {
+ }
+ mCanTakePicture = true
+ focusArea(false, false)
+ }
+
+ private fun focusArea(takePictureAfter: Boolean, showFocusRect: Boolean = true) {
+ if (mCamera == null || (mIsFocusingBeforeCapture && !takePictureAfter)) {
+ return
+ }
+
+ if (takePictureAfter) {
+ mIsFocusingBeforeCapture = true
+ }
+
+ mCamera!!.cancelAutoFocus()
+ if (mParameters!!.maxNumFocusAreas > 0) {
+ if (mLastClickX == 0f && mLastClickY == 0f) {
+ mLastClickX = width / 2.toFloat()
+ mLastClickY = height / 2.toFloat()
+ }
+
+ val focusRect = calculateFocusArea(mLastClickX, mLastClickY)
+ val focusAreas = ArrayList(1)
+ focusAreas.add(Camera.Area(focusRect, 1000))
+ mParameters!!.focusAreas = focusAreas
+
+ if (showFocusRect) {
+ mActivity!!.drawFocusCircle(mLastClickX, mLastClickY)
+ }
+ }
+
+ try {
+ val focusModes = mParameters!!.supportedFocusModes
+ if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
+ mParameters!!.focusMode = Camera.Parameters.FOCUS_MODE_AUTO
+ }
+
+ updateCameraParameters()
+ mCamera!!.autoFocus { success, camera ->
+ if (camera == null || mCamera == null) {
+ return@autoFocus
+ }
+
+ mCamera!!.cancelAutoFocus()
+ if (focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+ mParameters!!.focusMode = Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE
+ }
+
+ updateCameraParameters()
+
+ if (takePictureAfter) {
+ takePicture()
+ } else {
+ rescheduleAutofocus()
+ }
+ }
+ } catch (ignored: Exception) {
+ }
+ }
+
+ private fun calculateFocusArea(x: Float, y: Float): Rect {
+ var left = x / mSurfaceView.width * 2000 - 1000
+ var top = y / mSurfaceView.height * 2000 - 1000
+
+ val tmp = left
+ left = top
+ top = -tmp
+
+ val rectLeft = Math.max(left.toInt() - FOCUS_AREA_SIZE / 2, -1000)
+ val rectTop = Math.max(top.toInt() - FOCUS_AREA_SIZE / 2, -1000)
+ val rectRight = Math.min(left.toInt() + FOCUS_AREA_SIZE / 2, 1000)
+ val rectBottom = Math.min(top.toInt() + FOCUS_AREA_SIZE / 2, 1000)
+ return Rect(rectLeft, rectTop, rectRight, rectBottom)
+ }
+
+ private fun rescheduleAutofocus() {
+ mAutoFocusHandler.removeCallbacksAndMessages(null)
+ mAutoFocusHandler.postDelayed({
+ if (!mIsInVideoMode || !mIsRecording) {
+ focusArea(false, false)
+ }
+ }, REFOCUS_PERIOD)
+ }
+
+ override fun showChangeResolutionDialog() {
+ if (mCamera != null) {
+ val oldResolution = getSelectedResolution()
+ val photoResolutions = mCamera!!.parameters.supportedPictureSizes.map { MySize(it.width, it.height) } as ArrayList
+ val videoSizes = mCamera!!.parameters.supportedVideoSizes ?: mCamera!!.parameters.supportedPreviewSizes
+ val videoResolutions = videoSizes.map { MySize(it.width, it.height) } as ArrayList
+ ChangeResolutionDialog(mActivity!!, getIsUsingFrontCamera(), photoResolutions, videoResolutions, false) {
+ if (oldResolution != getSelectedResolution()) {
+ refreshPreview()
+ }
+ }
+ }
+ }
+
+ fun releaseCamera() {
+ stopRecording()
+ mCamera?.stopPreview()
+ mCamera?.release()
+ mCamera = null
+ cleanupRecorder()
+ }
+
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ mIsSurfaceCreated = true
+ try {
+ mWasCameraPreviewSet = mCamera != null
+ mCamera?.setPreviewDisplay(mSurfaceHolder)
+
+ if (mSwitchToVideoAsap)
+ initVideoMode()
+ } catch (e: IOException) {
+ mActivity!!.showErrorToast(e)
+ }
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ mIsSurfaceCreated = true
+
+ if (mIsInVideoMode) {
+ initVideoMode()
+ }
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ mIsSurfaceCreated = false
+ mCamera?.stopPreview()
+ cleanupRecorder()
+ }
+
+ private fun setupPreview() {
+ mCanTakePicture = true
+ if (mCamera != null && mPreviewSize != null) {
+ if (mParameters == null)
+ mParameters = mCamera!!.parameters
+
+ mParameters!!.setPreviewSize(mPreviewSize!!.width, mPreviewSize!!.height)
+ updateCameraParameters()
+ try {
+ mCamera!!.startPreview()
+ } catch (e: RuntimeException) {
+ mActivity!!.showErrorToast(e)
+ }
+ }
+ }
+
+ private fun cleanupRecorder() {
+ if (mRecorder != null) {
+ if (mIsRecording) {
+ stopRecording()
+ }
+
+ mRecorder!!.release()
+ mRecorder = null
+ }
+ }
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
+
+ private fun getOptimalPreviewSize(sizes: List, width: Int, height: Int): Camera.Size {
+ var result: Camera.Size? = null
+ for (size in sizes) {
+ if (size.width <= width && size.height <= height) {
+ if (result == null) {
+ result = size
+ } else {
+ val resultArea = result.width * result.height
+ val newArea = size.width * size.height
+
+ if (newArea > resultArea) {
+ result = size
+ }
+ }
+ }
+ }
+
+ return result!!
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ setMeasuredDimension(mScreenSize.x, mScreenSize.y)
+
+ if (mSupportedPreviewSizes != null) {
+ // for simplicity lets assume that most displays are 16:9 and the remaining ones are 4:3
+ // always set 16:9 for videos as many devices support 4:3 only in low quality
+ mPreviewSize = if (mIsSixteenToNine || mIsInVideoMode) {
+ getOptimalPreviewSize(mSupportedPreviewSizes!!, mScreenSize.y, mScreenSize.x)
+ } else {
+ val newRatioHeight = (mScreenSize.x * (4.toDouble() / 3)).toInt()
+ setMeasuredDimension(mScreenSize.x, newRatioHeight)
+ getOptimalPreviewSize(mSupportedPreviewSizes!!, newRatioHeight, mScreenSize.x)
+ }
+ val lp = mSurfaceView.layoutParams
+
+ // make sure to occupy whole width in every case
+ if (mScreenSize.x > mPreviewSize!!.height) {
+ val ratio = mScreenSize.x.toFloat() / mPreviewSize!!.height
+ lp.width = (mPreviewSize!!.height * ratio).toInt()
+ if (mIsSixteenToNine || mIsInVideoMode) {
+ lp.height = mScreenSize.y
+ } else {
+ lp.height = (mPreviewSize!!.width * ratio).toInt()
+ }
+ } else {
+ lp.width = mPreviewSize!!.height
+ lp.height = mPreviewSize!!.width
+ }
+
+ if (mSetupPreviewAfterMeasure) {
+ if (mCamera != null) {
+ mSetupPreviewAfterMeasure = false
+ mCamera!!.stopPreview()
+ setupPreview()
+ }
+ }
+ }
+ }
+
+ override fun setFlashlightState(state: Int) {
+ mFlashlightState = state
+ checkFlashlight()
+ }
+
+ override fun toggleFlashlight() {
+ val newState = ++mFlashlightState % if (mIsInVideoMode) 2 else 3
+ setFlashlightState(newState)
+ }
+
+ override fun checkFlashlight() {
+ when (mFlashlightState) {
+ FLASH_OFF -> disableFlash()
+ FLASH_ON -> enableFlash()
+ FLASH_AUTO -> setAutoFlash()
+ }
+ mActivity?.updateFlashlightState(mFlashlightState)
+ }
+
+ private fun disableFlash() {
+ mFlashlightState = FLASH_OFF
+ mParameters?.flashMode = Camera.Parameters.FLASH_MODE_OFF
+ updateCameraParameters()
+ }
+
+ private fun enableFlash() {
+ mFlashlightState = FLASH_ON
+ mParameters?.flashMode = Camera.Parameters.FLASH_MODE_TORCH
+ updateCameraParameters()
+ }
+
+ private fun setAutoFlash() {
+ mFlashlightState = FLASH_AUTO
+ mParameters?.flashMode = Camera.Parameters.FLASH_MODE_OFF
+ updateCameraParameters()
+
+ Handler().postDelayed({
+ mActivity?.runOnUiThread {
+ mParameters?.flashMode = Camera.Parameters.FLASH_MODE_AUTO
+ }
+ }, 1000)
+ }
+
+ override fun initPhotoMode() {
+ stopRecording()
+ cleanupRecorder()
+ mIsRecording = false
+ mIsInVideoMode = false
+ refreshPreview()
+ }
+
+ // VIDEO RECORDING
+ override fun initVideoMode(): Boolean {
+ if (mCamera == null || mRecorder != null || !mIsSurfaceCreated) {
+ return false
+ }
+
+ refreshPreview()
+ mSwitchToVideoAsap = false
+
+ mIsRecording = false
+ mIsInVideoMode = true
+ mRecorder = MediaRecorder().apply {
+ setCamera(mCamera)
+ setVideoSource(MediaRecorder.VideoSource.DEFAULT)
+ setAudioSource(MediaRecorder.AudioSource.DEFAULT)
+ }
+
+ mCurrVideoPath = mActivity!!.getOutputMediaFile(false)
+ if (mCurrVideoPath.isEmpty()) {
+ mActivity?.toast(R.string.video_creating_error)
+ return false
+ }
+
+ if (mRecorder == null) {
+ mActivity?.toast(R.string.unknown_error_occurred)
+ return false
+ }
+
+ val resolution = getSelectedResolution()
+ val profile = if (CamcorderProfile.hasProfile(CamcorderProfile.QUALITY_HIGH)) {
+ CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH)
+ } else {
+ CamcorderProfile.get(CamcorderProfile.QUALITY_LOW)
+ }
+
+ if (profile == null) {
+ mActivity?.toast(R.string.unknown_error_occurred)
+ return false
+ }
+
+ profile.apply {
+ videoFrameWidth = resolution.width
+ videoFrameHeight = resolution.height
+ mRecorder!!.setProfile(this)
+ }
+
+ checkPermissions()
+ if (mRecorder == null) {
+ return false
+ }
+ mRecorder!!.setPreviewDisplay(mSurfaceHolder.surface)
+
+ val rotation = getVideoRotation()
+ mRecorder!!.setOrientationHint(rotation)
+
+ try {
+ mRecorder!!.prepare()
+ } catch (e: Exception) {
+ setupFailed(e)
+ return false
+ }
+
+ return true
+ }
+
+ private fun checkPermissions(): Boolean {
+ if (mActivity!!.needsStupidWritePermissions(mCurrVideoPath)) {
+ if (mConfig.treeUri.isEmpty()) {
+ mActivity!!.toast(R.string.save_error_internal_storage)
+ mConfig.savePhotosFolder = Environment.getExternalStorageDirectory().toString()
+ releaseCamera()
+ return false
+ }
+
+ try {
+ var document = mActivity!!.getDocumentFile(mCurrVideoPath.getParentPath())
+ if (document == null) {
+ mActivity!!.toast(R.string.unknown_error_occurred)
+ return false
+ }
+
+ document = document.createFile("video/mp4", mCurrVideoPath.substring(mCurrVideoPath.lastIndexOf('/') + 1))
+ val fileDescriptor = context.contentResolver.openFileDescriptor(document!!.uri, "rw")
+ mRecorder!!.setOutputFile(fileDescriptor!!.fileDescriptor)
+ } catch (e: Exception) {
+ setupFailed(e)
+ }
+ } else {
+ mRecorder!!.setOutputFile(mCurrVideoPath)
+ }
+ return true
+ }
+
+ private fun setupFailed(e: Exception) {
+ mActivity!!.showErrorToast(e)
+ releaseCamera()
+ }
+
+ private fun updateCameraParameters() {
+ try {
+ mCamera?.parameters = mParameters
+ } catch (e: RuntimeException) {
+ }
+ }
+
+ override fun setTargetUri(uri: Uri) {
+ mTargetUri = uri
+ }
+
+ override fun setIsImageCaptureIntent(isImageCaptureIntent: Boolean) {
+ mIsImageCaptureIntent = isImageCaptureIntent
+ }
+
+ override fun toggleRecording() {
+ if (mIsRecording) {
+ stopRecording()
+ initVideoMode()
+ } else {
+ startRecording()
+ }
+ }
+
+ private fun getVideoRotation(): Int {
+ val deviceRot = compensateDeviceRotation(mActivity!!.mLastHandledOrientation, getIsUsingFrontCamera())
+ val previewRot = getPreviewRotation(mCurrCameraId)
+ return (deviceRot + previewRot) % 360
+ }
+
+ override fun deviceOrientationChanged() {
+ if (mIsInVideoMode && !mIsRecording) {
+ mRecorder = null
+ initVideoMode()
+ }
+ }
+
+ private fun startRecording() {
+ try {
+ mCamera!!.unlock()
+ toggleShutterSound(true)
+ mRecorder!!.start()
+ toggleShutterSound(false)
+ mIsRecording = true
+ mActivity!!.setRecordingState(true)
+ } catch (e: Exception) {
+ mActivity!!.showErrorToast(e)
+ releaseCamera()
+ }
+ }
+
+ private fun stopRecording() {
+ if (mRecorder != null && mIsRecording) {
+ try {
+ toggleShutterSound(true)
+ mRecorder!!.stop()
+ mActivity!!.rescanPaths(arrayListOf(mCurrVideoPath)) {
+ mActivity!!.videoSaved(Uri.fromFile(File(mCurrVideoPath)))
+ toggleShutterSound(false)
+ }
+ } catch (e: RuntimeException) {
+ mActivity!!.showErrorToast(e)
+ toggleShutterSound(false)
+ File(mCurrVideoPath).delete()
+ mRecorder = null
+ mIsRecording = false
+ releaseCamera()
+ }
+ }
+
+ mRecorder = null
+ if (mIsRecording) {
+ mActivity!!.setRecordingState(false)
+ }
+ mIsRecording = false
+
+ val file = File(mCurrVideoPath)
+ if (file.exists() && file.length() == 0L) {
+ file.delete()
+ }
+ }
+
+ private fun toggleShutterSound(mute: Boolean?) {
+ if (!mConfig.isSoundEnabled) {
+ (mActivity!!.getSystemService(Context.AUDIO_SERVICE) as AudioManager).setStreamMute(AudioManager.STREAM_SYSTEM, mute!!)
+ }
+ }
+
+ private fun hasFlash(camera: Camera?): Boolean {
+ if (camera == null) {
+ return false
+ }
+
+ if (camera.parameters.flashMode == null) {
+ return false
+ }
+
+ val supportedFlashModes = camera.parameters.supportedFlashModes
+ if (supportedFlashModes == null || supportedFlashModes.isEmpty() ||
+ supportedFlashModes.size == 1 && supportedFlashModes[0] == Camera.Parameters.FLASH_MODE_OFF) {
+ return false
+ }
+
+ return true
+ }
+
+ private fun getScreenSize(): Point {
+ val display = mActivity!!.windowManager.defaultDisplay
+ val size = Point()
+ display.getSize(size)
+ size.y += mActivity!!.resources.getNavBarHeight()
+ return size
+ }
+
+ private fun getPreviewRotation(cameraId: Int): Int {
+ val info = getCameraInfo(cameraId)
+ val degrees = when (mActivity!!.windowManager.defaultDisplay.rotation) {
+ Surface.ROTATION_90 -> 90
+ Surface.ROTATION_180 -> 180
+ Surface.ROTATION_270 -> 270
+ else -> 0
+ }
+
+ var result: Int
+ if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
+ result = (info.orientation + degrees) % 360
+ result = 360 - result
+ } else {
+ result = info.orientation - degrees + 360
+ }
+
+ return result % 360
+ }
+
+ private fun getCameraInfo(cameraId: Int): Camera.CameraInfo {
+ val info = android.hardware.Camera.CameraInfo()
+ Camera.getCameraInfo(cameraId, info)
+ return info
+ }
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/PreviewCameraTwo.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/PreviewCameraTwo.kt
new file mode 100644
index 0000000..54303a0
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/newcam/views/PreviewCameraTwo.kt
@@ -0,0 +1,1042 @@
+package com.mindorks.waprofileimage.waprofileImage.newcam.views
+
+import android.annotation.SuppressLint
+import android.annotation.TargetApi
+import android.content.Context
+import android.graphics.ImageFormat
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.graphics.SurfaceTexture
+import android.hardware.camera2.*
+import android.hardware.camera2.params.MeteringRectangle
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.media.ImageReader
+import android.media.MediaActionSound
+import android.media.MediaRecorder
+import android.net.Uri
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.util.DisplayMetrics
+import android.util.Range
+import android.util.Size
+import android.util.SparseIntArray
+import android.view.MotionEvent
+import android.view.Surface
+import android.view.TextureView
+import android.view.ViewGroup
+import com.mindorks.waprofileimage.waprofileImage.newcam.activities.MainActivity
+import com.mindorks.waprofileimage.waprofileImage.newcam.dialogs.ChangeResolutionDialog
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.config
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.getMyCamera
+import com.mindorks.waprofileimage.waprofileImage.newcam.extensions.getOutputMediaFile
+import com.mindorks.waprofileimage.waprofileImage.newcam.helpers.*
+import com.mindorks.waprofileimage.waprofileImage.newcam.interfaces.MyPreview
+import com.mindorks.waprofileimage.waprofileImage.newcam.models.FocusArea
+import com.mindorks.waprofileimage.waprofileImage.newcam.models.MySize
+import com.simplemobiletools.commons.extensions.*
+import com.simplemobiletools.commons.helpers.isJellyBean1Plus
+import com.simplemobiletools.commons.models.FileDirItem
+import java.io.File
+import java.lang.IllegalArgumentException
+import java.lang.InterruptedException
+import java.lang.Math
+import java.lang.System
+import java.lang.Thread
+import java.util.*
+import java.util.concurrent.Semaphore
+import java.util.concurrent.TimeUnit
+import kotlin.RuntimeException
+
+// based on the Android Camera2 photo sample at https://github.com/googlesamples/android-Camera2Basic
+// and video sample at https://github.com/googlesamples/android-Camera2Video
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+class PreviewCameraTwo : ViewGroup, TextureView.SurfaceTextureListener, MyPreview {
+ private val FOCUS_TAG = "focus_tag"
+ private val MAX_PREVIEW_WIDTH = 1920
+ private val MAX_PREVIEW_HEIGHT = 1080
+ private val MAX_VIDEO_WIDTH = 4096
+ private val MAX_VIDEO_HEIGHT = 2160
+ private val CLICK_MS = 250
+ private val CLICK_DIST = 20
+
+ private val DEFAULT_ORIENTATIONS = SparseIntArray(4).apply {
+ append(Surface.ROTATION_0, 90)
+ append(Surface.ROTATION_90, 0)
+ append(Surface.ROTATION_180, 270)
+ append(Surface.ROTATION_270, 180)
+ }
+
+ private val INVERSE_ORIENTATIONS = SparseIntArray(4).apply {
+ append(Surface.ROTATION_0, 270)
+ append(Surface.ROTATION_90, 180)
+ append(Surface.ROTATION_180, 90)
+ append(Surface.ROTATION_270, 0)
+ }
+
+ private lateinit var mActivity: MainActivity
+ private lateinit var mTextureView: AutoFitTextureView
+
+ private var mSensorOrientation = 0
+ private var mRotationAtCapture = 0
+ private var mZoomLevel = 1
+ private var mZoomFingerSpacing = 0f
+ private var mDownEventAtMS = 0L
+ private var mDownEventAtX = 0f
+ private var mDownEventAtY = 0f
+ private var mLastFocusX = 0f
+ private var mLastFocusY = 0f
+ private var mIsFlashSupported = true
+ private var mIsZoomSupported = true
+ private var mIsFocusSupported = true
+ private var mIsImageCaptureIntent = false
+ private var mIsInVideoMode = false
+ private var mIsRecording = false
+ private var mUseFrontCamera = false
+ private var mCameraId = ""
+ private var mLastVideoPath = ""
+ private var mCameraState = STATE_INIT
+ private var mFlashlightState = FLASH_OFF
+
+ private var mBackgroundThread: HandlerThread? = null
+ private var mBackgroundHandler: Handler? = null
+ private var mImageReader: ImageReader? = null
+ private var mPreviewSize: Size? = null
+ private var mTargetUri: Uri? = null
+ private var mCameraDevice: CameraDevice? = null
+ private var mCaptureSession: CameraCaptureSession? = null
+ private var mPreviewRequestBuilder: CaptureRequest.Builder? = null
+ private var mPreviewRequest: CaptureRequest? = null
+ private var mMediaRecorder: MediaRecorder? = null
+ private val mCameraToPreviewMatrix = Matrix()
+ private val mPreviewToCameraMatrix = Matrix()
+ private val mCameraOpenCloseLock = Semaphore(1)
+ private val mMediaActionSound = MediaActionSound()
+ private var mZoomRect: Rect? = null
+
+ constructor(context: Context) : super(context)
+
+ @SuppressLint("ClickableViewAccessibility")
+ constructor(activity: MainActivity, textureView: AutoFitTextureView, initPhotoMode: Boolean) : super(activity) {
+ mActivity = activity
+ mTextureView = textureView
+ val cameraCharacteristics = try {
+ getCameraCharacteristics(activity.config.lastUsedCamera)
+ } catch (e: IllegalArgumentException) {
+ mActivity.showErrorToast("Get camera characteristics $e")
+ null
+ }
+
+ val isFrontCamera = cameraCharacteristics?.get(CameraCharacteristics.LENS_FACING).toString() == activity.getMyCamera().getFrontCameraId().toString()
+ mUseFrontCamera = !activity.config.alwaysOpenBackCamera && isFrontCamera
+ mIsInVideoMode = !initPhotoMode
+ loadSounds()
+
+ mTextureView.setOnTouchListener { view, event ->
+ if (event.action == MotionEvent.ACTION_DOWN) {
+ mDownEventAtMS = System.currentTimeMillis()
+ mDownEventAtX = event.x
+ mDownEventAtY = event.y
+ } else if (event.action == MotionEvent.ACTION_UP) {
+ if (mIsFocusSupported && System.currentTimeMillis() - mDownEventAtMS < CLICK_MS &&
+ mCaptureSession != null &&
+ Math.abs(event.x - mDownEventAtX) < CLICK_DIST &&
+ Math.abs(event.y - mDownEventAtY) < CLICK_DIST) {
+ try {
+ focusArea(event.x, event.y, true)
+ } catch (e: Exception) {
+ }
+ }
+ }
+
+ if (mIsZoomSupported && event.pointerCount > 1 && mCaptureSession != null) {
+ try {
+ handleZoom(event)
+ } catch (e: Exception) {
+ }
+ }
+ true
+ }
+ }
+
+ override fun onResumed() {
+ startBackgroundThread()
+
+ if (mTextureView.isAvailable) {
+ openCamera(mTextureView.width, mTextureView.height)
+ } else {
+ mTextureView.surfaceTextureListener = this
+ }
+ }
+
+ override fun onPaused() {
+ closeCamera()
+ stopBackgroundThread()
+ }
+
+ override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture?, width: Int, height: Int) {
+ closeCamera()
+ openCamera(width, height)
+ }
+
+ override fun onSurfaceTextureUpdated(surface: SurfaceTexture?) {}
+
+ override fun onSurfaceTextureDestroyed(surface: SurfaceTexture) = true
+
+ override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
+ openCamera(width, height)
+ }
+
+ private fun startBackgroundThread() {
+ mBackgroundThread = HandlerThread("SimpleCameraBackground")
+ mBackgroundThread!!.start()
+ mBackgroundHandler = Handler(mBackgroundThread!!.looper)
+ }
+
+ private fun stopBackgroundThread() {
+ try {
+ mBackgroundThread?.quitSafely()
+ mBackgroundThread?.join()
+ mBackgroundThread = null
+ mBackgroundHandler = null
+ } catch (e: InterruptedException) {
+ }
+ }
+
+ private fun loadSounds() {
+ mMediaActionSound.load(MediaActionSound.START_VIDEO_RECORDING)
+ mMediaActionSound.load(MediaActionSound.STOP_VIDEO_RECORDING)
+ mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK)
+ }
+
+ @SuppressLint("MissingPermission")
+ private fun openCamera(width: Int, height: Int) {
+ try {
+ mActivity.runOnUiThread {
+ setupCameraOutputs(width, height)
+ if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
+ throw RuntimeException("Time out waiting to lock camera opening.")
+ }
+ getCameraManager().openCamera(mCameraId, cameraStateCallback, mBackgroundHandler)
+ }
+ } catch (e: Exception) {
+ mActivity.showErrorToast("Open camera $e")
+ }
+ }
+
+ private fun closeCamera() {
+ try {
+ mCameraOpenCloseLock.acquire()
+ mCaptureSession?.close()
+ mCaptureSession = null
+ mCameraDevice?.close()
+ mCameraDevice = null
+ mImageReader?.close()
+ mImageReader = null
+ mMediaRecorder?.release()
+ mMediaRecorder = null
+ } catch (e: Exception) {
+ } finally {
+ mCameraOpenCloseLock.release()
+ }
+ }
+
+ private fun closeCaptureSession() {
+ mCaptureSession?.close()
+ mCaptureSession = null
+ }
+
+ private fun handleZoom(event: MotionEvent) {
+ val maxZoom = getCameraCharacteristics().get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) * 10
+ val sensorRect = getCameraCharacteristics(mCameraId).get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
+ val currentFingerSpacing = getFingerSpacing(event)
+ if (mZoomFingerSpacing != 0f) {
+ if (currentFingerSpacing > mZoomFingerSpacing && maxZoom > mZoomLevel) {
+ mZoomLevel++
+ } else if (currentFingerSpacing < mZoomFingerSpacing && mZoomLevel > 1) {
+ mZoomLevel--
+ }
+
+ val minWidth = sensorRect.width() / maxZoom
+ val minHeight = sensorRect.height() / maxZoom
+ val diffWidth = sensorRect.width() - minWidth
+ val diffHeight = sensorRect.height() - minHeight
+ var cropWidth = (diffWidth / 100 * mZoomLevel).toInt()
+ var cropHeight = (diffHeight / 100 * mZoomLevel).toInt()
+ cropWidth -= cropWidth and 3
+ cropHeight -= cropHeight and 3
+ mZoomRect = Rect(cropWidth, cropHeight, sensorRect.width() - cropWidth, sensorRect.height() - cropHeight)
+ mPreviewRequestBuilder!!.set(CaptureRequest.SCALER_CROP_REGION, mZoomRect)
+ mPreviewRequest = mPreviewRequestBuilder!!.build()
+ mCaptureSession!!.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler)
+ }
+ mZoomFingerSpacing = currentFingerSpacing
+ }
+
+ private fun getFingerSpacing(event: MotionEvent): Float {
+ val x = event.getX(0) - event.getX(1)
+ val y = event.getY(0) - event.getY(1)
+ return Math.sqrt((x * x + y * y).toDouble()).toFloat()
+ }
+
+ private val imageAvailableListener = ImageReader.OnImageAvailableListener { reader ->
+ try {
+ val image = reader.acquireNextImage()
+ val buffer = image.planes.first().buffer
+ val bytes = ByteArray(buffer.remaining())
+ buffer.get(bytes)
+ image.close()
+ PhotoProcessor(mActivity, mTargetUri, mRotationAtCapture, mSensorOrientation, mUseFrontCamera, mIsImageCaptureIntent).execute(bytes)
+ } catch (e: Exception) {
+ }
+ }
+
+ private fun getCurrentResolution(): MySize {
+ val configMap = getCameraCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
+ val resIndex = if (mUseFrontCamera) {
+ if (mIsInVideoMode) {
+ mActivity.config.frontVideoResIndex
+ } else {
+ mActivity.config.frontPhotoResIndex
+ }
+ } else {
+ if (mIsInVideoMode) {
+ mActivity.config.backVideoResIndex
+ } else {
+ mActivity.config.backPhotoResIndex
+ }
+ }
+
+ val outputSizes = if (mIsInVideoMode) {
+ getAvailableVideoSizes(configMap).toTypedArray()
+ } else {
+ configMap.getOutputSizes(ImageFormat.JPEG)
+ }
+
+ val size = outputSizes.sortedByDescending { it.width * it.height }[resIndex]
+ return MySize(size.width, size.height)
+ }
+
+ private fun setupCameraOutputs(width: Int, height: Int) {
+ val manager = getCameraManager()
+ try {
+ for (cameraId in manager.cameraIdList) {
+ val characteristics = getCameraCharacteristics(cameraId)
+
+ val facing = characteristics.get(CameraCharacteristics.LENS_FACING) ?: continue
+ if ((mUseFrontCamera && facing == CameraCharacteristics.LENS_FACING_BACK) || (!mUseFrontCamera && facing == CameraCharacteristics.LENS_FACING_FRONT)) {
+ continue
+ }
+
+ mCameraId = cameraId
+ mActivity.config.lastUsedCamera = mCameraId
+ val configMap = getCameraCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
+ val currentResolution = getCurrentResolution()
+
+ if (mIsInVideoMode) {
+ mImageReader = null
+ mMediaRecorder = MediaRecorder()
+ } else {
+ mImageReader = ImageReader.newInstance(currentResolution.width, currentResolution.height, ImageFormat.JPEG, 2)
+ mImageReader!!.setOnImageAvailableListener(imageAvailableListener, null)
+ mMediaRecorder = null
+ }
+
+ val displaySize = getRealDisplaySize()
+ var maxPreviewWidth = displaySize.width
+ var maxPreviewHeight = displaySize.height
+ var rotatedPreviewWidth = width
+ var rotatedPreviewHeight = height
+
+ mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!
+ if (mSensorOrientation == 90 || mSensorOrientation == 270) {
+ rotatedPreviewWidth = height
+ rotatedPreviewHeight = width
+
+ val tmpWidth = maxPreviewWidth
+ maxPreviewWidth = maxPreviewHeight
+ maxPreviewHeight = tmpWidth
+ }
+
+ if (maxPreviewWidth > MAX_PREVIEW_WIDTH) {
+ maxPreviewWidth = MAX_PREVIEW_WIDTH
+ }
+
+ if (maxPreviewHeight > MAX_PREVIEW_HEIGHT) {
+ maxPreviewHeight = MAX_PREVIEW_HEIGHT
+ }
+
+ val outputSizes = if (mIsInVideoMode) {
+ getAvailableVideoSizes(configMap).toTypedArray()
+ } else {
+ configMap.getOutputSizes(SurfaceTexture::class.java)
+ }
+
+ mPreviewSize = chooseOptimalPreviewSize(outputSizes, rotatedPreviewWidth, rotatedPreviewHeight, maxPreviewWidth, maxPreviewHeight, currentResolution)
+
+ mActivity.runOnUiThread {
+ mTextureView.setAspectRatio(mPreviewSize!!.height, mPreviewSize!!.width)
+ }
+ characteristics.apply {
+ mIsFlashSupported = get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false
+ mIsZoomSupported = get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM) ?: 0f > 0f
+ mIsFocusSupported = get(CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES).size > 1
+ }
+ mActivity.setFlashAvailable(mIsFlashSupported)
+ mActivity.updateCameraIcon(mUseFrontCamera)
+ return
+ }
+ } catch (e: Exception) {
+ mActivity.showErrorToast("Setup camera outputs $e")
+ }
+ }
+
+ private fun chooseOptimalPreviewSize(choices: Array, textureViewWidth: Int, textureViewHeight: Int, maxWidth: Int, maxHeight: Int, selectedResolution: MySize): Size {
+ val bigEnough = ArrayList()
+ val notBigEnough = ArrayList()
+ val width = selectedResolution.width
+ val height = selectedResolution.height
+ for (option in choices) {
+ if (option.width <= maxWidth && option.height <= maxHeight && option.height == option.width * height / width) {
+ if (option.width >= textureViewWidth && option.height >= textureViewHeight) {
+ bigEnough.add(option)
+ } else {
+ notBigEnough.add(option)
+ }
+ }
+ }
+
+ return when {
+ bigEnough.isNotEmpty() -> bigEnough.minBy { it.width * it.height }!!
+ notBigEnough.isNotEmpty() -> notBigEnough.maxBy { it.width * it.height }!!
+ else -> selectedResolution.toSize()
+ }
+ }
+
+ private fun getRealDisplaySize(): MySize {
+ val metrics = DisplayMetrics()
+ return if (isJellyBean1Plus()) {
+ mActivity.windowManager.defaultDisplay.getRealMetrics(metrics)
+ MySize(metrics.widthPixels, metrics.heightPixels)
+ } else {
+ mActivity.windowManager.defaultDisplay.getMetrics(metrics)
+ MySize(metrics.widthPixels, metrics.heightPixels)
+ }
+ }
+
+ private val cameraStateCallback = object : CameraDevice.StateCallback() {
+ override fun onOpened(cameraDevice: CameraDevice) {
+ mCameraOpenCloseLock.release()
+ mCameraDevice = cameraDevice
+ createCameraPreviewSession()
+ mActivity.setIsCameraAvailable(true)
+ }
+
+ override fun onDisconnected(cameraDevice: CameraDevice) {
+ mCameraOpenCloseLock.release()
+ cameraDevice.close()
+ mCameraDevice = null
+ mActivity.setIsCameraAvailable(false)
+ }
+
+ override fun onError(cameraDevice: CameraDevice, error: Int) {
+ mCameraOpenCloseLock.release()
+ cameraDevice.close()
+ mCameraDevice = null
+ mActivity.setIsCameraAvailable(false)
+ }
+ }
+
+ private fun createCameraPreviewSession() {
+ val stateCallback = object : CameraCaptureSession.StateCallback() {
+ override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
+ if (mCameraDevice == null) {
+ return
+ }
+
+ mCaptureSession = cameraCaptureSession
+ try {
+ mPreviewRequestBuilder!!.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, getFrameRange())
+ if (mIsInVideoMode) {
+ mPreviewRequestBuilder!!.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO)
+ mCaptureSession!!.setRepeatingRequest(mPreviewRequestBuilder!!.build(), null, mBackgroundHandler)
+ } else {
+ mPreviewRequestBuilder!!.apply {
+ set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+ setFlashAndExposure(this)
+ mPreviewRequest = build()
+ }
+ mCaptureSession!!.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler)
+ }
+ mCameraState = STATE_PREVIEW
+ } catch (e: Exception) {
+ }
+ }
+
+ override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {}
+ }
+
+ try {
+ closeCaptureSession()
+ val texture = mTextureView.surfaceTexture!!
+ texture.setDefaultBufferSize(mPreviewSize!!.width, mPreviewSize!!.height)
+
+ val surface = Surface(texture)
+ mPreviewRequestBuilder = mCameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
+ mPreviewRequestBuilder!!.set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW)
+ mPreviewRequestBuilder!!.addTarget(surface)
+
+ if (mIsInVideoMode) {
+ mCameraDevice!!.createCaptureSession(Arrays.asList(surface), stateCallback, mBackgroundHandler)
+ } else {
+ mCameraDevice!!.createCaptureSession(Arrays.asList(surface, mImageReader!!.surface), stateCallback, null)
+ }
+ } catch (e: Exception) {
+ }
+ }
+
+ private fun getFrameRange(): Range {
+ val ranges = getCameraCharacteristics().get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES)
+ var currRangeSize = -1
+ var currMinRange = 0
+ var result: Range? = null
+ for (range in ranges) {
+ val diff = range.upper - range.lower
+ if (diff > currRangeSize || (diff == currRangeSize && range.lower > currMinRange)) {
+ currRangeSize = diff
+ currMinRange = range.lower
+ result = range
+ }
+ }
+
+ return result!!
+ }
+
+ private fun updatePreview() {
+ try {
+ mPreviewRequestBuilder!!.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO)
+ mCaptureSession!!.setRepeatingRequest(mPreviewRequestBuilder!!.build(), null, mBackgroundHandler)
+ } catch (e: CameraAccessException) {
+ }
+ }
+
+ private val mCaptureCallback = object : CameraCaptureSession.CaptureCallback() {
+ private fun process(result: CaptureResult) {
+ when (mCameraState) {
+ STATE_WAITING_LOCK -> {
+ val autoFocusState = result.get(CaptureResult.CONTROL_AF_STATE)
+ if (autoFocusState == null) {
+ captureStillPicture()
+ } else if (autoFocusState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED || autoFocusState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
+ val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
+ if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) {
+ captureStillPicture()
+ } else {
+ runPrecaptureSequence()
+ }
+ }
+ }
+ STATE_WAITING_PRECAPTURE -> {
+ val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
+ if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) {
+ mCameraState = STATE_WAITING_NON_PRECAPTURE
+ }
+ }
+ STATE_WAITING_NON_PRECAPTURE -> {
+ val aeState = result.get(CaptureResult.CONTROL_AE_STATE)
+ if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) {
+ captureStillPicture()
+ }
+ }
+ }
+ }
+
+ override fun onCaptureProgressed(session: CameraCaptureSession, request: CaptureRequest, partialResult: CaptureResult) {
+ process(partialResult)
+ }
+
+ override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
+ process(result)
+ }
+ }
+
+ private fun runPrecaptureSequence() {
+ try {
+ mPreviewRequestBuilder!!.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER, CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START)
+ mCameraState = STATE_WAITING_PRECAPTURE
+ mCaptureSession!!.capture(mPreviewRequestBuilder!!.build(), mCaptureCallback, mBackgroundHandler)
+ } catch (e: CameraAccessException) {
+ }
+ }
+
+ private fun captureStillPicture() {
+ try {
+ if (mCameraDevice == null) {
+ return
+ }
+
+ if (mActivity.config.isSoundEnabled) {
+ mMediaActionSound.play(MediaActionSound.SHUTTER_CLICK)
+ }
+
+ mCameraState = STATE_PICTURE_TAKEN
+ mRotationAtCapture = mActivity.mLastHandledOrientation
+ val jpegOrientation = (mSensorOrientation + compensateDeviceRotation(mRotationAtCapture, mUseFrontCamera)) % 360
+ val captureBuilder = mCameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE).apply {
+ addTarget(mImageReader!!.surface)
+ setFlashAndExposure(this)
+ set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+ set(CaptureRequest.JPEG_ORIENTATION, jpegOrientation)
+ set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_STILL_CAPTURE)
+ set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, getFrameRange())
+ if (mZoomRect != null) {
+ set(CaptureRequest.SCALER_CROP_REGION, mZoomRect)
+ }
+ }
+
+ val captureCallback = object : CameraCaptureSession.CaptureCallback() {
+ override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
+ unlockFocus()
+ mActivity.toggleBottomButtons(false)
+ }
+
+ override fun onCaptureFailed(session: CameraCaptureSession?, request: CaptureRequest?, failure: CaptureFailure?) {
+ super.onCaptureFailed(session, request, failure)
+ mActivity.toggleBottomButtons(false)
+ }
+ }
+
+ mCaptureSession?.apply {
+ stopRepeating()
+ abortCaptures()
+ capture(captureBuilder.build(), captureCallback, null)
+ }
+ } catch (e: CameraAccessException) {
+ mActivity.showErrorToast("Capture picture $e")
+ }
+ }
+
+ // inspired by https://gist.github.com/royshil/8c760c2485257c85a11cafd958548482
+ private fun focusArea(x: Float, y: Float, drawCircle: Boolean) {
+ mLastFocusX = x
+ mLastFocusY = y
+ if (drawCircle) {
+ mActivity.drawFocusCircle(x, y)
+ }
+
+ val captureCallbackHandler = object : CameraCaptureSession.CaptureCallback() {
+ override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
+ super.onCaptureCompleted(session, request, result)
+
+ if (request.tag == FOCUS_TAG) {
+ mCaptureSession?.setRepeatingRequest(mPreviewRequestBuilder!!.build(), mCaptureCallback, mBackgroundHandler)
+ }
+ }
+ }
+
+ mCaptureSession!!.stopRepeating()
+ mPreviewRequestBuilder!!.apply {
+ set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE)
+ mCaptureSession!!.capture(build(), mCaptureCallback, mBackgroundHandler)
+
+ // touch-to-focus inspired by OpenCamera
+ val characteristics = getCameraCharacteristics()
+ if (characteristics.get(CameraCharacteristics.CONTROL_MAX_REGIONS_AF) >= 1) {
+ val focusArea = getFocusArea(x, y)
+ val sensorRect = characteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE)
+ val meteringRect = convertAreaToMeteringRectangle(sensorRect, focusArea)
+ set(CaptureRequest.CONTROL_AF_REGIONS, arrayOf(meteringRect))
+ }
+
+ set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO)
+ set(CaptureRequest.CONTROL_AF_MODE, CameraMetadata.CONTROL_AF_MODE_AUTO)
+ set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START)
+ setTag(FOCUS_TAG)
+ mCaptureSession!!.capture(build(), captureCallbackHandler, mBackgroundHandler)
+ set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE)
+ }
+ }
+
+ private fun convertAreaToMeteringRectangle(sensorRect: Rect, focusArea: FocusArea): MeteringRectangle {
+ val camera2Rect = convertRectToCamera2(sensorRect, focusArea.rect)
+ return MeteringRectangle(camera2Rect, focusArea.weight)
+ }
+
+ private fun convertRectToCamera2(cropRect: Rect, rect: Rect): Rect {
+ val leftF = (rect.left + 1000) / 2000f
+ val topF = (rect.top + 1000) / 2000f
+ val rightF = (rect.right + 1000) / 2000f
+ val bottomF = (rect.bottom + 1000) / 2000f
+ var left = (cropRect.left + leftF * (cropRect.width() - 1)).toInt()
+ var right = (cropRect.left + rightF * (cropRect.width() - 1)).toInt()
+ var top = (cropRect.top + topF * (cropRect.height() - 1)).toInt()
+ var bottom = (cropRect.top + bottomF * (cropRect.height() - 1)).toInt()
+ left = Math.max(left, cropRect.left)
+ right = Math.max(right, cropRect.left)
+ top = Math.max(top, cropRect.top)
+ bottom = Math.max(bottom, cropRect.top)
+ left = Math.min(left, cropRect.right)
+ right = Math.min(right, cropRect.right)
+ top = Math.min(top, cropRect.bottom)
+ bottom = Math.min(bottom, cropRect.bottom)
+
+ return Rect(left, top, right, bottom)
+ }
+
+ private fun getFocusArea(x: Float, y: Float): FocusArea {
+ val coords = floatArrayOf(x, y)
+ calculateCameraToPreviewMatrix()
+ mPreviewToCameraMatrix.mapPoints(coords)
+ val focusX = coords[0].toInt()
+ val focusY = coords[1].toInt()
+
+ val focusSize = 50
+ val rect = Rect()
+ rect.left = focusX - focusSize
+ rect.right = focusX + focusSize
+ rect.top = focusY - focusSize
+ rect.bottom = focusY + focusSize
+
+ if (rect.left < -1000) {
+ rect.left = -1000
+ rect.right = rect.left + 2 * focusSize
+ } else if (rect.right > 1000) {
+ rect.right = 1000
+ rect.left = rect.right - 2 * focusSize
+ }
+
+ if (rect.top < -1000) {
+ rect.top = -1000
+ rect.bottom = rect.top + 2 * focusSize
+ } else if (rect.bottom > 1000) {
+ rect.bottom = 1000
+ rect.top = rect.bottom - 2 * focusSize
+ }
+
+ return FocusArea(rect, MeteringRectangle.METERING_WEIGHT_MAX)
+ }
+
+ // touch-to-focus stucks after capturing a photo without "Focus before capture" so just reset the whole session until fixed properly
+ private fun resetPreviewSession() {
+ val stateCallback = object : CameraCaptureSession.StateCallback() {
+ override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
+ if (mCameraDevice == null) {
+ return
+ }
+
+ mCaptureSession = cameraCaptureSession
+ try {
+ mPreviewRequestBuilder!!.apply {
+ set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+ setFlashAndExposure(this)
+ mPreviewRequest = build()
+ }
+ mCaptureSession!!.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler)
+ mCameraState = STATE_PREVIEW
+
+ Handler().postDelayed({
+ if (mLastFocusX != 0f && mLastFocusY != 0f) {
+ if (mCaptureSession != null) {
+ focusArea(mLastFocusX, mLastFocusY, false)
+ }
+ }
+ }, 200L)
+ } catch (e: Exception) {
+ }
+ }
+
+ override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {}
+ }
+
+ try {
+ closeCaptureSession()
+ val texture = mTextureView.surfaceTexture!!
+ texture.setDefaultBufferSize(mPreviewSize!!.width, mPreviewSize!!.height)
+
+ val currentResolution = getCurrentResolution()
+ mImageReader = ImageReader.newInstance(currentResolution.width, currentResolution.height, ImageFormat.JPEG, 2)
+ mImageReader!!.setOnImageAvailableListener(imageAvailableListener, mBackgroundHandler)
+
+ val surface = Surface(texture)
+ mCameraDevice!!.createCaptureSession(Arrays.asList(surface, mImageReader!!.surface), stateCallback, null)
+ } catch (e: Exception) {
+ }
+ }
+
+ private fun calculateCameraToPreviewMatrix() {
+ val yScale = if (mUseFrontCamera) -1 else 1
+ mCameraToPreviewMatrix.apply {
+ reset()
+ setScale(1f, yScale.toFloat())
+ postRotate(mSensorOrientation.toFloat())
+ postScale(mTextureView.width / 2000f, mTextureView.height / 2000f)
+ postTranslate(mTextureView.width / 2f, mTextureView.height / 2f)
+ invert(mPreviewToCameraMatrix)
+ }
+ }
+
+ private fun lockFocus() {
+ try {
+ mPreviewRequestBuilder!!.apply {
+ set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_START)
+ mCameraState = STATE_WAITING_LOCK
+ mCaptureSession!!.capture(build(), mCaptureCallback, mBackgroundHandler)
+ }
+ } catch (e: CameraAccessException) {
+ mCameraState = STATE_PREVIEW
+ }
+ }
+
+ private fun unlockFocus() {
+ try {
+ mPreviewRequestBuilder!!.apply {
+ set(CaptureRequest.CONTROL_AF_TRIGGER, CameraMetadata.CONTROL_AF_TRIGGER_IDLE)
+ mCaptureSession!!.capture(build(), mCaptureCallback, mBackgroundHandler)
+ }
+ mCameraState = STATE_PREVIEW
+ mCaptureSession!!.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler)
+
+ if (mLastFocusX != 0f && mLastFocusY != 0f) {
+ focusArea(mLastFocusX, mLastFocusY, false)
+ }
+ } catch (e: CameraAccessException) {
+ } finally {
+ mCameraState = STATE_PREVIEW
+ }
+ }
+
+ private fun setFlashAndExposure(builder: CaptureRequest.Builder) {
+ val aeMode = if (mFlashlightState == FLASH_AUTO) CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH else CameraMetadata.CONTROL_AE_MODE_ON
+ builder.apply {
+ set(CaptureRequest.FLASH_MODE, getFlashlightMode())
+ set(CaptureRequest.CONTROL_AE_MODE, aeMode)
+ }
+ }
+
+ private fun getCameraManager() = mActivity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
+
+ private fun getCameraCharacteristics(cameraId: String = mCameraId) = getCameraManager().getCameraCharacteristics(cameraId)
+
+ private fun getFlashlightMode() = when (mFlashlightState) {
+ FLASH_ON -> CameraMetadata.FLASH_MODE_TORCH
+ else -> CameraMetadata.FLASH_MODE_OFF
+ }
+
+ private fun setupMediaRecorder() {
+ try {
+ val videoSize = getCurrentResolution()
+ mLastVideoPath = mActivity.getOutputMediaFile(false)
+ val rotation = mActivity.windowManager.defaultDisplay.rotation
+ mMediaRecorder!!.apply {
+ setAudioSource(MediaRecorder.AudioSource.MIC)
+ setVideoSource(MediaRecorder.VideoSource.SURFACE)
+ setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
+ setOutputFile(mLastVideoPath)
+ setVideoEncodingBitRate(10000000)
+ setVideoFrameRate(30)
+ setVideoSize(videoSize.width, videoSize.height)
+ setVideoEncoder(MediaRecorder.VideoEncoder.H264)
+ setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
+ when (mSensorOrientation) {
+ 90 -> setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation))
+ 270 -> setOrientationHint(INVERSE_ORIENTATIONS.get(rotation))
+ }
+ prepare()
+ }
+ } catch (e: Exception) {
+ mActivity.showErrorToast(e)
+ }
+ }
+
+ private fun startRecording() {
+ mCameraState = STATE_STARTING_RECORDING
+ closeCaptureSession()
+ setupMediaRecorder()
+ if (mActivity.config.isSoundEnabled) {
+ mMediaActionSound.play(MediaActionSound.START_VIDEO_RECORDING)
+ }
+
+ val texture = mTextureView.surfaceTexture
+ texture.setDefaultBufferSize(mPreviewSize!!.width, mPreviewSize!!.height)
+ mPreviewRequestBuilder = mCameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
+ set(CaptureRequest.CONTROL_CAPTURE_INTENT, CaptureRequest.CONTROL_CAPTURE_INTENT_VIDEO_RECORD)
+ set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE, getFrameRange())
+ }
+
+ val surfaces = ArrayList()
+ val previewSurface = Surface(texture)
+ surfaces.add(previewSurface)
+ mPreviewRequestBuilder!!.addTarget(previewSurface)
+
+ val recorderSurface = mMediaRecorder!!.surface
+ surfaces.add(recorderSurface)
+ mPreviewRequestBuilder!!.addTarget(recorderSurface)
+
+ val captureCallback = object : CameraCaptureSession.StateCallback() {
+ override fun onConfigured(session: CameraCaptureSession?) {
+ mCaptureSession = session
+ updatePreview()
+ mIsRecording = true
+ mActivity.runOnUiThread {
+ mMediaRecorder?.start()
+ }
+ mActivity.setRecordingState(true)
+ mCameraState = STATE_RECORDING
+ }
+
+ override fun onConfigureFailed(session: CameraCaptureSession?) {
+ mCameraState = STATE_PREVIEW
+ }
+ }
+
+ try {
+ mCameraDevice!!.createCaptureSession(surfaces, captureCallback, mBackgroundHandler)
+ } catch (e: Exception) {
+ mActivity.showErrorToast(e)
+ mCameraState = STATE_PREVIEW
+ }
+ }
+
+ private fun stopRecording() {
+ mCameraState = STATE_STOPING_RECORDING
+ if (mActivity.config.isSoundEnabled) {
+ mMediaActionSound.play(MediaActionSound.STOP_VIDEO_RECORDING)
+ }
+
+ mIsRecording = false
+ try {
+ mMediaRecorder!!.stop()
+ mMediaRecorder!!.reset()
+ mActivity.rescanPaths(arrayListOf(mLastVideoPath)) {
+ mActivity.videoSaved(Uri.fromFile(File(mLastVideoPath)))
+ }
+ } catch (e: Exception) {
+ //mActivity.toast(R.string.change_resolution_holder, Toast.LENGTH_LONG)
+ openResolutionsDialog(true)
+ val fileDirItem = FileDirItem(mLastVideoPath, mLastVideoPath.getFilenameFromPath())
+ mActivity.deleteFile(fileDirItem, false)
+ } finally {
+ Thread {
+ closeCamera()
+ openCamera(mTextureView.width, mTextureView.height)
+ }.start()
+ mActivity.setRecordingState(false)
+ }
+ }
+
+ private fun getAvailableVideoSizes(configMap: StreamConfigurationMap) = configMap.getOutputSizes(MediaRecorder::class.java).filter {
+ it.width <= MAX_VIDEO_WIDTH && it.height <= MAX_VIDEO_HEIGHT
+ }
+
+ private fun shouldLockFocus() = mIsFocusSupported && mActivity.config.focusBeforeCapture
+
+ override fun setTargetUri(uri: Uri) {
+ mTargetUri = uri
+ }
+
+ override fun setIsImageCaptureIntent(isImageCaptureIntent: Boolean) {
+ mIsImageCaptureIntent = isImageCaptureIntent
+ }
+
+ override fun setFlashlightState(state: Int) {
+ mFlashlightState = state
+ checkFlashlight()
+ }
+
+ override fun getCameraState() = mCameraState
+
+ override fun showChangeResolutionDialog() {
+ openResolutionsDialog(false)
+ }
+
+ private fun openResolutionsDialog(openVideoResolutions: Boolean) {
+ val oldResolution = getCurrentResolution()
+ val configMap = getCameraCharacteristics().get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
+ val photoResolutions = configMap.getOutputSizes(ImageFormat.JPEG).map { MySize(it.width, it.height) } as ArrayList
+ val videoResolutions = getAvailableVideoSizes(configMap).map { MySize(it.width, it.height) } as ArrayList
+ ChangeResolutionDialog(mActivity, mUseFrontCamera, photoResolutions, videoResolutions, openVideoResolutions) {
+ if (oldResolution != getCurrentResolution()) {
+ if (mIsRecording) {
+ stopRecording()
+ }
+ closeCamera()
+ openCamera(mTextureView.width, mTextureView.height)
+ }
+ }
+ }
+
+ override fun toggleFrontBackCamera() {
+ mUseFrontCamera = !mUseFrontCamera
+ closeCamera()
+ openCamera(mTextureView.width, mTextureView.height)
+ }
+
+ override fun toggleFlashlight() {
+ val newState = ++mFlashlightState % if (mIsInVideoMode) 2 else 3
+ setFlashlightState(newState)
+ }
+
+ override fun tryTakePicture() {
+ if (mCameraState != STATE_PREVIEW) {
+ return
+ }
+
+ if (shouldLockFocus()) {
+ lockFocus()
+ } else {
+ captureStillPicture()
+ }
+ }
+
+ override fun toggleRecording() {
+ if (mCameraDevice == null || !mTextureView.isAvailable || mPreviewSize == null) {
+ return
+ }
+
+ if (mCameraState != STATE_PREVIEW && mCameraState != STATE_RECORDING) {
+ return
+ }
+
+ if (mIsRecording) {
+ stopRecording()
+ } else {
+ startRecording()
+ }
+ }
+
+ override fun tryInitVideoMode() {
+ initVideoMode()
+ }
+
+ override fun initPhotoMode() {
+ mIsInVideoMode = false
+ closeCamera()
+ openCamera(mTextureView.width, mTextureView.height)
+ }
+
+ override fun initVideoMode(): Boolean {
+ mLastFocusX = 0f
+ mLastFocusY = 0f
+ mIsInVideoMode = true
+ closeCamera()
+ openCamera(mTextureView.width, mTextureView.height)
+ return true
+ }
+
+ override fun checkFlashlight() {
+ if ((mCameraState == STATE_PREVIEW || mCameraState == STATE_RECORDING) && mIsFlashSupported) {
+ setFlashAndExposure(mPreviewRequestBuilder!!)
+ mPreviewRequest = mPreviewRequestBuilder!!.build()
+ mCaptureSession?.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler)
+ mActivity.updateFlashlightState(mFlashlightState)
+ }
+ }
+
+ override fun deviceOrientationChanged() {}
+
+ override fun resumeCamera() = true
+
+ override fun imageSaved() {}
+
+ override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {}
+}
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAPProfileImageActivityPresenter.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAPProfileImageActivityPresenter.kt
new file mode 100644
index 0000000..3d1dafb
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAPProfileImageActivityPresenter.kt
@@ -0,0 +1,115 @@
+package com.mindorks.waprofileimage.waprofileImage.wapprofileimageactivity
+
+import android.content.Context
+import android.support.v4.app.FragmentActivity
+import android.view.Gravity
+import android.view.View
+import android.widget.Toast
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.customdialog.DialogCustomization
+import com.mindorks.waprofileimage.customdialog.ListHolder
+import com.mindorks.waprofileimage.customdialog.SimpleAdapter
+import com.mindorks.waprofileimage.customdialog.callback.*
+import com.mindorks.waprofileimage.waprofileImage.WAProfileImage.RESPONSE_CODE_OPEN_GALLERY
+import com.mindorks.waprofileimage.waprofileImage.WAProfileImage.RESPONSE_CODE_REMOVE_IMAGE
+import com.mindorks.waprofileimage.waprofileImage.camera.Common
+
+class WAPProfileImageActivityPresenter constructor(wAProfileImageView: WAProfileImageView, ctx: Context)
+ : WAProfileImagePresnterInterface, OnClickListener,
+ OnDismissListener,
+ OnItemClickListener, OnCancelListener, OnBackPressListener {
+ var requestCode: Int? = null
+ var context: Context
+
+ var view: WAProfileImageView? = null
+
+ init {
+ view = wAProfileImageView
+ context = ctx
+
+ }
+
+ override fun launchOptionDialog(context: FragmentActivity, requestCode: Int) {
+ showCustomeDialog(context, requestCode)
+
+ }
+
+ override fun onClick(dialog: DialogCustomization, view: View) {
+ TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
+ }
+
+ override fun onDismiss(dialog: DialogCustomization) {
+
+ view!!.onViewFinished()
+ }
+
+ override fun onItemClick(dialog: DialogCustomization, item: Any, view: View, position: Int) {
+ dialog.dismiss()
+ handleEachItemClicked(position)
+ }
+
+ override fun onCancel(dialog: DialogCustomization) {
+ view!!.onViewFinished()
+ }
+
+ override fun onBackPressed(dialog: DialogCustomization) {
+ view!!.onViewFinished()
+ }
+
+ private fun showCustomeDialog(context: FragmentActivity, requestCode: Int) {
+ this.requestCode = requestCode
+ val holder = ListHolder()
+ val adapter = SimpleAdapter(context, 3)
+ val builder = DialogCustomization.newDialog(context).apply {
+ setContentHolder(holder)
+ setHeader(R.layout.header, true)
+ setFooter(R.layout.footer, true)
+ setCancelable(true)
+ setGravity(Gravity.BOTTOM)
+ setAdapter(adapter)
+ setOnClickListener(this@WAPProfileImageActivityPresenter)
+ setOnItemClickListener(this@WAPProfileImageActivityPresenter)
+ setOnCancelListener(this@WAPProfileImageActivityPresenter)
+ setOnDismissListener(this@WAPProfileImageActivityPresenter)
+ setOnBackPressListener(this@WAPProfileImageActivityPresenter)
+ setExpanded(false, 450)
+ setOverlayBackgroundResource(android.R.color.transparent)
+ }
+
+ builder.create().show()
+
+
+ }
+
+ private fun handleEachItemClicked(position: Int) {
+ when (position) {
+ -1 -> {
+ // TODO open Gallary and get the image path and set the new Result Code
+ view!!.setNewResult(RESPONSE_CODE_OPEN_GALLERY)
+ Toast.makeText(context, "Open Gallery", Toast.LENGTH_SHORT).show()
+
+ }
+ 0 -> {
+ // view!!.setNewResult(WAProfileImageActivity.RESPONSE_CODE_OPEN_CAMERA)
+ showCameraView()
+
+ }
+ 1 -> {
+ // TODO Remove the image and get image path
+ view!!.setNewResult(RESPONSE_CODE_REMOVE_IMAGE)
+ Toast.makeText(context, "Remove Photo", Toast.LENGTH_SHORT).show()
+
+ }
+ }
+
+
+ }
+
+ private fun showCameraView() {
+ /** Check if this device has a camera */
+ // if (!Common.checkCameraHardware(context)) view!!.writeMessage(context.getString(R.string.no_camera_error_message)); return
+
+ view!!.openCamera()
+ }
+
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAProfileImageActivity.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAProfileImageActivity.kt
new file mode 100644
index 0000000..34957f5
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAProfileImageActivity.kt
@@ -0,0 +1,72 @@
+package com.mindorks.waprofileimage.waprofileImage.wapprofileimageactivity
+
+import android.content.Intent
+import android.os.Bundle
+import android.support.v4.app.FragmentActivity
+import android.support.v7.app.AppCompatActivity
+import android.view.MotionEvent
+import android.view.View
+import android.widget.Toast
+import com.mindorks.waprofileimage.R
+import com.mindorks.waprofileimage.waprofileImage.WAProfileImage.REQUEST_CODE_DEFAULT_VALUE
+import com.mindorks.waprofileimage.waprofileImage.WAProfileImage.REQUEST_CODE_KEY
+import com.mindorks.waprofileimage.waprofileImage.camera.CameraActivity
+
+class WAProfileImageActivity : AppCompatActivity(), WAProfileImageView {
+
+ override fun writeMessage(message: String) {
+ Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+ }
+
+
+
+ private lateinit var presenter: WAPProfileImageActivityPresenter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_waprofile_image)
+ setTitle(null)
+ presenter = WAPProfileImageActivityPresenter(this, this)
+ presenter.launchOptionDialog(this, intent.getIntExtra(REQUEST_CODE_KEY, REQUEST_CODE_DEFAULT_VALUE))
+
+
+ }
+
+ override fun openCamera() {
+ val i = Intent(this, CameraActivity::class.java)
+ startActivityForResult(i,intent.getIntExtra(REQUEST_CODE_KEY, REQUEST_CODE_DEFAULT_VALUE))
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ super.onActivityResult(requestCode, resultCode, data)
+ // this for from camera activity it will be handled
+ if (data != null) {
+ setNewResult(resultCode,data)
+ }else{
+ setNewResult(resultCode)
+ }
+ finish()
+ }
+
+ override fun setNewResult(resultCode: Int) {
+ setResult(resultCode)
+ }
+
+ override fun setNewResult(resultCode: Int, data: Intent) {
+ setResult(resultCode, data)
+
+ }
+
+ override fun onViewFinished() {
+ // finish()
+ }
+
+ override fun launch(context: FragmentActivity, requestCode: Int) {
+ TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
+ }
+
+ override fun onTouch(p0: View?, p1: MotionEvent?): Boolean {
+ TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
+ }
+
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAProfileImagePresnterInterface.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAProfileImagePresnterInterface.kt
new file mode 100644
index 0000000..dab2c7f
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAProfileImagePresnterInterface.kt
@@ -0,0 +1,7 @@
+package com.mindorks.waprofileimage.waprofileImage.wapprofileimageactivity
+
+import android.support.v4.app.FragmentActivity
+
+interface WAProfileImagePresnterInterface {
+ fun launchOptionDialog(context: FragmentActivity, requestCode: Int)
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAProfileImageView.kt b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAProfileImageView.kt
new file mode 100644
index 0000000..6e4521c
--- /dev/null
+++ b/whatsappprofileimage/src/main/java/com/mindorks/waprofileimage/waprofileImage/wapprofileimageactivity/WAProfileImageView.kt
@@ -0,0 +1,17 @@
+package com.mindorks.waprofileimage.waprofileImage.wapprofileimageactivity
+
+import android.content.Intent
+import android.support.v4.app.FragmentActivity
+import android.view.MotionEvent
+import android.view.View
+
+interface WAProfileImageView :View.OnTouchListener{
+ fun launch(context: FragmentActivity, requestCode: Int)
+ override fun onTouch(p0: View?, p1: MotionEvent?): Boolean
+ fun onViewFinished()
+ fun setNewResult(resultCode:Int)
+ fun setNewResult(resultCode:Int,data:Intent)
+ fun writeMessage(message:String)
+ fun openCamera()
+
+}
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/anim/slide_in_bottom.xml b/whatsappprofileimage/src/main/res/anim/slide_in_bottom.xml
new file mode 100644
index 0000000..ba663a1
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/anim/slide_in_bottom.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/anim/slide_out_bottom.xml b/whatsappprofileimage/src/main/res/anim/slide_out_bottom.xml
new file mode 100644
index 0000000..335c4c4
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/anim/slide_out_bottom.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_camera_front.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_camera_front.png
new file mode 100644
index 0000000..f36dcd4
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_camera_front.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_camera_rear.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_camera_rear.png
new file mode 100644
index 0000000..4a5494f
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_camera_rear.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_flash_auto.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_flash_auto.png
new file mode 100644
index 0000000..3eb32b2
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_flash_auto.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_flash_off.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_flash_off.png
new file mode 100644
index 0000000..948446b
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_flash_off.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_flash_on.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_flash_on.png
new file mode 100644
index 0000000..4747a11
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_flash_on.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_resolution.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_resolution.png
new file mode 100644
index 0000000..719ebf7
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_resolution.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_settings_cog.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_settings_cog.png
new file mode 100644
index 0000000..97ded33
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_settings_cog.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_shutter.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_shutter.png
new file mode 100644
index 0000000..0fe586a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_shutter.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_video.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_video.png
new file mode 100644
index 0000000..d83e0d5
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_video.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_video_rec.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_video_rec.png
new file mode 100644
index 0000000..3555237
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_video_rec.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-hdpi/ic_video_stop.png b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_video_stop.png
new file mode 100644
index 0000000..72847bb
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-hdpi/ic_video_stop.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-v26/ic_launcher_foreground.xml b/whatsappprofileimage/src/main/res/drawable-v26/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2fa81ce
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/drawable-v26/ic_launcher_foreground.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_camera_front.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_camera_front.png
new file mode 100644
index 0000000..a80ccde
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_camera_front.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_camera_rear.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_camera_rear.png
new file mode 100644
index 0000000..b0768e3
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_camera_rear.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_flash_auto.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_flash_auto.png
new file mode 100644
index 0000000..185fa40
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_flash_auto.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_flash_off.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_flash_off.png
new file mode 100644
index 0000000..23c854e
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_flash_off.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_flash_on.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_flash_on.png
new file mode 100644
index 0000000..bedc4eb
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_flash_on.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_resolution.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_resolution.png
new file mode 100644
index 0000000..4577f75
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_resolution.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_settings_cog.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_settings_cog.png
new file mode 100644
index 0000000..5caedc8
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_settings_cog.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_shutter.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_shutter.png
new file mode 100644
index 0000000..d371655
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_shutter.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_video.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_video.png
new file mode 100644
index 0000000..1b2583d
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_video.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_video_rec.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_video_rec.png
new file mode 100644
index 0000000..6cebd9b
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_video_rec.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_video_stop.png b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_video_stop.png
new file mode 100644
index 0000000..4726160
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xhdpi/ic_video_stop.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_camera_front.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_camera_front.png
new file mode 100644
index 0000000..3eb24d1
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_camera_front.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_camera_rear.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_camera_rear.png
new file mode 100644
index 0000000..8392b2a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_camera_rear.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_flash_auto.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_flash_auto.png
new file mode 100644
index 0000000..087aa59
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_flash_auto.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_flash_off.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_flash_off.png
new file mode 100644
index 0000000..3cf30f3
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_flash_off.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_flash_on.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_flash_on.png
new file mode 100644
index 0000000..4e116af
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_flash_on.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_resolution.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_resolution.png
new file mode 100644
index 0000000..2f6ca2c
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_resolution.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_settings_cog.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_settings_cog.png
new file mode 100644
index 0000000..eabb0a2
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_settings_cog.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_shutter.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_shutter.png
new file mode 100644
index 0000000..acee078
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_shutter.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_video.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_video.png
new file mode 100644
index 0000000..44c28e2
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_video.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_video_rec.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_video_rec.png
new file mode 100644
index 0000000..b833e67
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_video_rec.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_video_stop.png b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_video_stop.png
new file mode 100644
index 0000000..c689689
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxhdpi/ic_video_stop.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_camera_front.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_camera_front.png
new file mode 100644
index 0000000..951bc12
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_camera_front.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_camera_rear.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_camera_rear.png
new file mode 100644
index 0000000..8b2415b
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_camera_rear.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_flash_auto.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_flash_auto.png
new file mode 100644
index 0000000..899276f
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_flash_auto.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_flash_off.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_flash_off.png
new file mode 100644
index 0000000..1d774f2
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_flash_off.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_flash_on.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_flash_on.png
new file mode 100644
index 0000000..cb60611
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_flash_on.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_resolution.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_resolution.png
new file mode 100644
index 0000000..f6ba9d0
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_resolution.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_settings_cog.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_settings_cog.png
new file mode 100644
index 0000000..507c5ed
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_settings_cog.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_shutter.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_shutter.png
new file mode 100644
index 0000000..1d69c89
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_shutter.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_video.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_video.png
new file mode 100644
index 0000000..ed20c07
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_video.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_video_rec.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_video_rec.png
new file mode 100644
index 0000000..e53f62f
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_video_rec.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_video_stop.png b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_video_stop.png
new file mode 100644
index 0000000..c3670ce
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable-xxxhdpi/ic_video_stop.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable/gallaery.png b/whatsappprofileimage/src/main/res/drawable/gallaery.png
new file mode 100644
index 0000000..8746b4b
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable/gallaery.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable/ic_google_maps_icon.png b/whatsappprofileimage/src/main/res/drawable/ic_google_maps_icon.png
new file mode 100644
index 0000000..3de6d3a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable/ic_google_maps_icon.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable/ic_google_messenger_icon.png b/whatsappprofileimage/src/main/res/drawable/ic_google_messenger_icon.png
new file mode 100644
index 0000000..0c65a95
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable/ic_google_messenger_icon.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable/ic_google_plus_icon.png b/whatsappprofileimage/src/main/res/drawable/ic_google_plus_icon.png
new file mode 100644
index 0000000..d132092
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable/ic_google_plus_icon.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable/ic_launcher.png b/whatsappprofileimage/src/main/res/drawable/ic_launcher.png
new file mode 100644
index 0000000..96a442e
Binary files /dev/null and b/whatsappprofileimage/src/main/res/drawable/ic_launcher.png differ
diff --git a/whatsappprofileimage/src/main/res/drawable/ring.xml b/whatsappprofileimage/src/main/res/drawable/ring.xml
new file mode 100644
index 0000000..3d1df52
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/drawable/ring.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/layout/activity_camera.xml b/whatsappprofileimage/src/main/res/layout/activity_camera.xml
new file mode 100644
index 0000000..ab4c47e
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/activity_camera.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/layout/activity_main.xml b/whatsappprofileimage/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..364f4b0
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/activity_main.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/layout/activity_settings.xml b/whatsappprofileimage/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..2cf3ccc
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,448 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/layout/activity_simple.xml b/whatsappprofileimage/src/main/res/layout/activity_simple.xml
new file mode 100644
index 0000000..8ec85c7
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/activity_simple.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/layout/activity_splash.xml b/whatsappprofileimage/src/main/res/layout/activity_splash.xml
new file mode 100644
index 0000000..22f83be
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/activity_splash.xml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/layout/activity_waprofile_image.xml b/whatsappprofileimage/src/main/res/layout/activity_waprofile_image.xml
new file mode 100644
index 0000000..92b208c
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/activity_waprofile_image.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/whatsappprofileimage/src/main/res/layout/base_container.xml b/whatsappprofileimage/src/main/res/layout/base_container.xml
new file mode 100644
index 0000000..f581691
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/base_container.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/layout/content_main.xml b/whatsappprofileimage/src/main/res/layout/content_main.xml
new file mode 100644
index 0000000..f82d020
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/content_main.xml
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/layout/dialog_change_resolution.xml b/whatsappprofileimage/src/main/res/layout/dialog_change_resolution.xml
new file mode 100644
index 0000000..fa32781
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/dialog_change_resolution.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/layout/dialog_list.xml b/whatsappprofileimage/src/main/res/layout/dialog_list.xml
new file mode 100644
index 0000000..1849142
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/dialog_list.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/layout/footer.xml b/whatsappprofileimage/src/main/res/layout/footer.xml
new file mode 100644
index 0000000..da700fe
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/footer.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/layout/fragment_camera2_basic.xml b/whatsappprofileimage/src/main/res/layout/fragment_camera2_basic.xml
new file mode 100644
index 0000000..b639c53
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/fragment_camera2_basic.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/layout/header.xml b/whatsappprofileimage/src/main/res/layout/header.xml
new file mode 100644
index 0000000..8fcc93b
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/header.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/layout/item_dialog.xml b/whatsappprofileimage/src/main/res/layout/item_dialog.xml
new file mode 100644
index 0000000..507b6ac
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/item_dialog.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/layout/layout_dialog.xml b/whatsappprofileimage/src/main/res/layout/layout_dialog.xml
new file mode 100644
index 0000000..989f51f
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/layout/layout_dialog.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/menu/menu.xml b/whatsappprofileimage/src/main/res/menu/menu.xml
new file mode 100644
index 0000000..0cce6d2
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/menu/menu.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..a77f722
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml
new file mode 100644
index 0000000..dab4c0c
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_amber.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml
new file mode 100644
index 0000000..37bf057
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_blue.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml
new file mode 100644
index 0000000..3e4d069
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_blue_grey.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml
new file mode 100644
index 0000000..9786d7b
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_brown.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml
new file mode 100644
index 0000000..afb3d0d
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_cyan.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml
new file mode 100644
index 0000000..1846b81
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_orange.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml
new file mode 100644
index 0000000..4152801
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_deep_purple.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml
new file mode 100644
index 0000000..e55d109
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_green.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml
new file mode 100644
index 0000000..40d0745
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_grey_black.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml
new file mode 100644
index 0000000..601d817
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_indigo.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml
new file mode 100644
index 0000000..01f2fea
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_light_blue.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml
new file mode 100644
index 0000000..d37b24c
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_light_green.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml
new file mode 100644
index 0000000..9fd7bc5
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_lime.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml
new file mode 100644
index 0000000..d2adf9a
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_pink.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml
new file mode 100644
index 0000000..32c838c
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_purple.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml
new file mode 100644
index 0000000..a6e9359
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_red.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml
new file mode 100644
index 0000000..18492d2
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_teal.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml
new file mode 100644
index 0000000..854427e
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/mipmap-anydpi-v26/ic_launcher_yellow.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..b4fd0a6
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_amber.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_amber.png
new file mode 100644
index 0000000..42e50cf
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_amber.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_blue.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_blue.png
new file mode 100644
index 0000000..ac6abf3
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_blue_grey.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_blue_grey.png
new file mode 100644
index 0000000..2d20b40
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_blue_grey.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_brown.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_brown.png
new file mode 100644
index 0000000..a9addc9
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_brown.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_cyan.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_cyan.png
new file mode 100644
index 0000000..ded2df1
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_cyan.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_deep_orange.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_deep_orange.png
new file mode 100644
index 0000000..503ea18
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_deep_orange.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_deep_purple.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_deep_purple.png
new file mode 100644
index 0000000..faa7227
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_deep_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..806d6d7
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_green.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_green.png
new file mode 100644
index 0000000..0ec20a5
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_grey_black.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_grey_black.png
new file mode 100644
index 0000000..61d62bc
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_grey_black.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_indigo.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_indigo.png
new file mode 100644
index 0000000..21ed5c9
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_indigo.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_light_blue.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_light_blue.png
new file mode 100644
index 0000000..07c253c
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_light_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_light_green.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_light_green.png
new file mode 100644
index 0000000..d025224
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_light_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_lime.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_lime.png
new file mode 100644
index 0000000..a4eb5a8
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_lime.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_pink.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_pink.png
new file mode 100644
index 0000000..2bd7bed
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_pink.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_purple.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_purple.png
new file mode 100644
index 0000000..c70cd1f
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_red.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_red.png
new file mode 100644
index 0000000..18bf5ec
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_red.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_teal.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_teal.png
new file mode 100644
index 0000000..9b3e647
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_teal.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_yellow.png b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_yellow.png
new file mode 100644
index 0000000..6c1e5a3
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-hdpi/ic_launcher_yellow.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..6f57b30
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_amber.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_amber.png
new file mode 100644
index 0000000..701ffdd
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_amber.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_blue.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_blue.png
new file mode 100644
index 0000000..8364cd1
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_blue_grey.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_blue_grey.png
new file mode 100644
index 0000000..25b4a4a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_blue_grey.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_brown.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_brown.png
new file mode 100644
index 0000000..55746e0
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_brown.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_cyan.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_cyan.png
new file mode 100644
index 0000000..0b61fc2
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_cyan.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_deep_orange.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_deep_orange.png
new file mode 100644
index 0000000..5699509
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_deep_orange.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_deep_purple.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_deep_purple.png
new file mode 100644
index 0000000..cc1df48
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_deep_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..baeb299
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_green.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_green.png
new file mode 100644
index 0000000..7434635
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_grey_black.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_grey_black.png
new file mode 100644
index 0000000..d2c8905
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_grey_black.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_indigo.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_indigo.png
new file mode 100644
index 0000000..94577b9
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_indigo.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_light_blue.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_light_blue.png
new file mode 100644
index 0000000..98881fc
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_light_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_light_green.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_light_green.png
new file mode 100644
index 0000000..309426a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_light_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_lime.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_lime.png
new file mode 100644
index 0000000..20fcc00
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_lime.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_pink.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_pink.png
new file mode 100644
index 0000000..c239a75
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_pink.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_purple.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_purple.png
new file mode 100644
index 0000000..daf7109
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_red.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_red.png
new file mode 100644
index 0000000..d2fa589
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_red.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_teal.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_teal.png
new file mode 100644
index 0000000..a0b75f8
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_teal.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_yellow.png b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_yellow.png
new file mode 100644
index 0000000..9e4b088
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-mdpi/ic_launcher_yellow.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..cc5d758
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_amber.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_amber.png
new file mode 100644
index 0000000..c56d1f2
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_amber.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_blue.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_blue.png
new file mode 100644
index 0000000..76d776d
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_blue_grey.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_blue_grey.png
new file mode 100644
index 0000000..67078fe
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_blue_grey.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_brown.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_brown.png
new file mode 100644
index 0000000..39e16bd
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_brown.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_cyan.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_cyan.png
new file mode 100644
index 0000000..e42e73b
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_cyan.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_deep_orange.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_deep_orange.png
new file mode 100644
index 0000000..97c29bc
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_deep_orange.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_deep_purple.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_deep_purple.png
new file mode 100644
index 0000000..6fff618
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_deep_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..d5f68a4
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_green.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_green.png
new file mode 100644
index 0000000..28f9d4d
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_grey_black.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_grey_black.png
new file mode 100644
index 0000000..7b97362
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_grey_black.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_indigo.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_indigo.png
new file mode 100644
index 0000000..3dd23db
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_indigo.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_light_blue.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_light_blue.png
new file mode 100644
index 0000000..5df9048
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_light_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_light_green.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_light_green.png
new file mode 100644
index 0000000..03ff77a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_light_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_lime.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_lime.png
new file mode 100644
index 0000000..261744b
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_lime.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_pink.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_pink.png
new file mode 100644
index 0000000..e4d9188
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_pink.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_purple.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_purple.png
new file mode 100644
index 0000000..8441caf
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_red.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_red.png
new file mode 100644
index 0000000..9985b2b
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_red.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_teal.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_teal.png
new file mode 100644
index 0000000..7e0ca08
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_teal.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_yellow.png b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_yellow.png
new file mode 100644
index 0000000..3318608
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xhdpi/ic_launcher_yellow.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..481bf42
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_amber.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_amber.png
new file mode 100644
index 0000000..bcc0782
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_amber.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_blue.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_blue.png
new file mode 100644
index 0000000..b0f5900
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_blue_grey.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_blue_grey.png
new file mode 100644
index 0000000..38bbf29
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_blue_grey.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png
new file mode 100644
index 0000000..c0ade62
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_brown.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_cyan.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_cyan.png
new file mode 100644
index 0000000..7cf930c
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_cyan.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_deep_orange.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_deep_orange.png
new file mode 100644
index 0000000..13765eb
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_deep_orange.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_deep_purple.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_deep_purple.png
new file mode 100644
index 0000000..d7a07d0
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_deep_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..7031d48
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_green.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_green.png
new file mode 100644
index 0000000..fba1714
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_grey_black.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_grey_black.png
new file mode 100644
index 0000000..f5ac90c
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_grey_black.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_indigo.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_indigo.png
new file mode 100644
index 0000000..305c26f
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_indigo.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_light_blue.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_light_blue.png
new file mode 100644
index 0000000..b896e8a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_light_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_light_green.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_light_green.png
new file mode 100644
index 0000000..5124f66
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_light_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_lime.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_lime.png
new file mode 100644
index 0000000..7778983
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_lime.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_pink.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_pink.png
new file mode 100644
index 0000000..9d5029b
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_pink.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png
new file mode 100644
index 0000000..ac8702a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_red.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_red.png
new file mode 100644
index 0000000..5e67224
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_red.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_teal.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_teal.png
new file mode 100644
index 0000000..619854a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_teal.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_yellow.png b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_yellow.png
new file mode 100644
index 0000000..a405b0a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxhdpi/ic_launcher_yellow.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..600271b
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_amber.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_amber.png
new file mode 100644
index 0000000..680719a
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_amber.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_blue.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_blue.png
new file mode 100644
index 0000000..6357caf
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_blue_grey.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_blue_grey.png
new file mode 100644
index 0000000..497f949
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_blue_grey.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png
new file mode 100644
index 0000000..374f3de
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_brown.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_cyan.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_cyan.png
new file mode 100644
index 0000000..a0307bb
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_cyan.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_orange.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_orange.png
new file mode 100644
index 0000000..b50b199
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_orange.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_purple.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_purple.png
new file mode 100644
index 0000000..624e02c
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_deep_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..1e8f878
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_green.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_green.png
new file mode 100644
index 0000000..f3fa701
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_grey_black.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_grey_black.png
new file mode 100644
index 0000000..48e5ffd
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_grey_black.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_indigo.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_indigo.png
new file mode 100644
index 0000000..ce8b7d3
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_indigo.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_light_blue.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_light_blue.png
new file mode 100644
index 0000000..4b05d84
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_light_blue.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_light_green.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_light_green.png
new file mode 100644
index 0000000..14c925f
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_light_green.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_lime.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_lime.png
new file mode 100644
index 0000000..24aef3e
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_lime.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_pink.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_pink.png
new file mode 100644
index 0000000..738c25d
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_pink.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png
new file mode 100644
index 0000000..d55b94e
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_purple.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png
new file mode 100644
index 0000000..bc6359f
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_red.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_teal.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_teal.png
new file mode 100644
index 0000000..2ca8b65
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_teal.png differ
diff --git a/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_yellow.png b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_yellow.png
new file mode 100644
index 0000000..9f43c07
Binary files /dev/null and b/whatsappprofileimage/src/main/res/mipmap-xxxhdpi/ic_launcher_yellow.png differ
diff --git a/whatsappprofileimage/src/main/res/values/colors.xml b/whatsappprofileimage/src/main/res/values/colors.xml
new file mode 100644
index 0000000..34134f7
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #D4D4D4
+ #60000000
+ #cc4285f4
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/values/dimen.xml b/whatsappprofileimage/src/main/res/values/dimen.xml
new file mode 100644
index 0000000..8029196
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/values/dimen.xml
@@ -0,0 +1,13 @@
+
+
+
+ 8dp
+ 12sp
+ 16dp
+ 20sp
+ 16dp
+ 140dp
+ 20dp
+ 56dp
+
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/values/donottranslate.xml b/whatsappprofileimage/src/main/res/values/donottranslate.xml
new file mode 100644
index 0000000..228061e
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/values/donottranslate.xml
@@ -0,0 +1,19 @@
+
+
+
+
+ Allow changing the photo compression quality
+ Added optional exif metadata saving, enabled by default
+ Added a toggle for flipping front camera photos horizontally
+
+ Add an option to focus the photo before capture\n
+ Add an option to use the volume buttons as shutter
+
+ Added an automatic flash mode
+
+ Added more color customization options\n
+ Your settings have been cleared, please reset them\n
+ Moved the resolution picker onto the main screen
+
+
+
diff --git a/whatsappprofileimage/src/main/res/values/integers.xml b/whatsappprofileimage/src/main/res/values/integers.xml
new file mode 100644
index 0000000..89d5b09
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/values/integers.xml
@@ -0,0 +1,4 @@
+
+
+ 300
+
\ No newline at end of file
diff --git a/whatsappprofileimage/src/main/res/values/strings.xml b/whatsappprofileimage/src/main/res/values/strings.xml
index d883f98..cf5ed94 100644
--- a/whatsappprofileimage/src/main/res/values/strings.xml
+++ b/whatsappprofileimage/src/main/res/values/strings.xml
@@ -1,3 +1,70 @@
-
- whatsappprofileimage
+
+ whatsappprofileimage
+ Gallery
+ Camera
+ Remove Photo
+ Profile photo
+ SAMPLE HEADER
+ No Camera Supported For This Device
+ Picture
+ This sample needs camera permission.
+ This device doesn\'t support Camera2 API.
+ MainActivity
+ SplashActivity
+
+
+
+
+
+ Camera
+ Camera unavailable
+ An error occurred accessing the camera
+ An error occurred creating the video file
+ Switching to video mode failed
+ An error occurred, save folder changed to internal storage
+ Switching camera failed
+ Click on the image to resume preview
+ The photo could not be saved
+ Setting proper resolution failed
+ Video recording failed, try using a different resolution
+
+
+ other
+
+
+ What photo compression quality should I set?
+ It depends on your goal. For generic purposes most people advise using 75%-80%, when the image is still really good quality, but the file size is reduced drastically compared to 100%.
+
+
+ Save photos and videos to
+ Show a photo preview after capturing
+ Shutter sound
+ Back camera resolutions
+ Front camera resolutions
+ Photo
+ Video
+ Focus before capture
+ Use volume buttons as shutter
+ Turn flash off at startup
+ Flip front camera photos horizontally
+ Keep the setting buttons visible
+ Always open the app with the Back camera
+ Save photo exif metadata
+ Photo compression quality
+ Shutter
+
+
+
+ A camera with flash, zoom and no ads.
+
+ The camera is usable for both photo taking and video recording. You can switch between front and rear camera, modify the save path and limit the resolution. The flash can be turned on and off or used as a flashlight. You can pinch to zoom in and out.
+
+ If you want to launch this app at pressing the hardware camera button, you might have to disable the built in Camera app in Settings -> Apps -> Camera -> Disable.
+
+ Contains no ads or unnecessary permissions. It is fully opensource, provides customizable colors.
+
+ This app is just one piece of a bigger series of apps. You can find the rest of them at https://www.simplemobiletools.com
+
+
+
diff --git a/whatsappprofileimage/src/main/res/values/styles.xml b/whatsappprofileimage/src/main/res/values/styles.xml
new file mode 100644
index 0000000..a379a26
--- /dev/null
+++ b/whatsappprofileimage/src/main/res/values/styles.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file