mirror of
https://github.com/PhilKes/NotallyX.git
synced 2025-06-30 05:09:53 +00:00
Compare commits
42 commits
Author | SHA1 | Date | |
---|---|---|---|
|
d00300fa0e | ||
|
f13e8227ca | ||
|
11e472cd30 | ||
|
4cc957ccd4 | ||
|
86b74762c5 | ||
|
118285545a | ||
|
402baf8056 | ||
|
de27d40880 | ||
|
66ce623e85 | ||
|
3c2400c7e6 | ||
|
c64a7b2ed7 | ||
|
fb687856f1 | ||
|
23d678c8a3 | ||
|
ade08b52ed | ||
|
62a35132e0 | ||
|
9fbe5a6b94 | ||
|
cf7f6f9dda | ||
|
01ac48f930 | ||
|
015f43e94b | ||
|
830fb6a75c | ||
|
c34ee3633e | ||
|
628bd9d564 | ||
|
3e889879fb | ||
|
b191618a46 | ||
|
5cbc62bdf7 | ||
|
d1e5770180 | ||
|
29cee8faf4 | ||
|
4f993af93f | ||
|
0f0eb80e9b | ||
|
1314ab4437 | ||
|
39022edfab | ||
|
157ecb1b13 | ||
|
fb35ffdac4 | ||
|
0fee25f022 | ||
|
2341c30586 | ||
|
e553e78efb | ||
|
724d08507a | ||
|
771546a0cb | ||
|
1a6d4083e4 | ||
|
3ac63349d8 | ||
|
06c48ab8d9 | ||
|
8d20f26eae |
41 changed files with 5852 additions and 5437 deletions
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -1,5 +1,23 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [v7.4.0](https://github.com/PhilKes/NotallyX/tree/v7.4.0) (2025-04-18)
|
||||||
|
|
||||||
|
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.3.1...v7.4.0)
|
||||||
|
|
||||||
|
### Added Features
|
||||||
|
|
||||||
|
- Don't force capitalization when adding a label [\#532](https://github.com/PhilKes/NotallyX/issues/532)
|
||||||
|
- Add a screen protection against screenshot attempts [\#386](https://github.com/PhilKes/NotallyX/issues/386)
|
||||||
|
|
||||||
|
### Fixed Bugs
|
||||||
|
|
||||||
|
- Share pure text note error [\#544](https://github.com/PhilKes/NotallyX/issues/544)
|
||||||
|
- Crash when deleting checked items in a list [\#539](https://github.com/PhilKes/NotallyX/issues/539)
|
||||||
|
- Keyboard don't open after closing it on Android 7 [\#537](https://github.com/PhilKes/NotallyX/issues/537)
|
||||||
|
- Unable to open links before changing view mode [\#527](https://github.com/PhilKes/NotallyX/issues/527)
|
||||||
|
- Reminder popup cut on small screens [\#522](https://github.com/PhilKes/NotallyX/issues/522)
|
||||||
|
- Auto Backup failed [\#514](https://github.com/PhilKes/NotallyX/issues/514)
|
||||||
|
|
||||||
## [v7.3.1](https://github.com/PhilKes/NotallyX/tree/v7.3.1) (2025-04-08)
|
## [v7.3.1](https://github.com/PhilKes/NotallyX/tree/v7.3.1) (2025-04-08)
|
||||||
|
|
||||||
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.3.0...v7.3.1)
|
[Full Changelog](https://github.com/PhilKes/NotallyX/compare/v7.3.0...v7.3.1)
|
||||||
|
|
|
@ -190,7 +190,7 @@ dependencies {
|
||||||
implementation("androidx.security:security-crypto:1.1.0-alpha06")
|
implementation("androidx.security:security-crypto:1.1.0-alpha06")
|
||||||
implementation("androidx.sqlite:sqlite-ktx:2.4.0")
|
implementation("androidx.sqlite:sqlite-ktx:2.4.0")
|
||||||
implementation("androidx.work:work-runtime:2.9.1")
|
implementation("androidx.work:work-runtime:2.9.1")
|
||||||
|
implementation("androidx.biometric:biometric:1.1.0")
|
||||||
implementation("cat.ereza:customactivityoncrash:2.4.0")
|
implementation("cat.ereza:customactivityoncrash:2.4.0")
|
||||||
implementation("com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0")
|
implementation("com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0")
|
||||||
implementation("com.github.bumptech.glide:glide:4.15.1")
|
implementation("com.github.bumptech.glide:glide:4.15.1")
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -77,6 +77,21 @@
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="*/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
|
||||||
|
<data android:scheme="file" android:mimeType="text/*" />
|
||||||
|
<data android:scheme="content" android:mimeType="text/*" />
|
||||||
|
|
||||||
|
<data android:scheme="file" android:mimeType="application/json" />
|
||||||
|
<data android:scheme="content" android:mimeType="application/json" />
|
||||||
|
|
||||||
|
<data android:scheme="file" android:mimeType="application/xml" />
|
||||||
|
<data android:scheme="content" android:mimeType="application/xml" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".presentation.activity.note.ViewImageActivity" />
|
<activity android:name=".presentation.activity.note.ViewImageActivity" />
|
||||||
|
|
|
@ -70,7 +70,7 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (oldTheme != null) {
|
if (oldTheme != null) {
|
||||||
WidgetProvider.updateWidgets(this)
|
WidgetProvider.updateWidgets(this, locked = locked.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -73,7 +73,7 @@ interface BaseNoteDao {
|
||||||
@Query("SELECT id, reminders FROM BaseNote WHERE reminders IS NOT NULL AND reminders != '[]'")
|
@Query("SELECT id, reminders FROM BaseNote WHERE reminders IS NOT NULL AND reminders != '[]'")
|
||||||
suspend fun getAllReminders(): List<NoteIdReminder>
|
suspend fun getAllReminders(): List<NoteIdReminder>
|
||||||
|
|
||||||
@Query("SELECT color FROM BaseNote WHERE id = :id ") fun getColorOfNote(id: Long): String
|
@Query("SELECT color FROM BaseNote WHERE id = :id ") fun getColorOfNote(id: Long): String?
|
||||||
|
|
||||||
@Query(
|
@Query(
|
||||||
"SELECT id, title, type, reminders FROM BaseNote WHERE reminders IS NOT NULL AND reminders != '[]'"
|
"SELECT id, title, type, reminders FROM BaseNote WHERE reminders IS NOT NULL AND reminders != '[]'"
|
||||||
|
|
|
@ -122,7 +122,7 @@ fun String.applySpans(representations: List<SpanRepresentation>): Editable {
|
||||||
->
|
->
|
||||||
try {
|
try {
|
||||||
if (bold) {
|
if (bold) {
|
||||||
editable.setSpan(StyleSpan(Typeface.BOLD), start, end)
|
editable.setSpan(createBoldSpan(), start, end)
|
||||||
}
|
}
|
||||||
if (italic) {
|
if (italic) {
|
||||||
editable.setSpan(StyleSpan(Typeface.ITALIC), start, end)
|
editable.setSpan(StyleSpan(Typeface.ITALIC), start, end)
|
||||||
|
@ -144,6 +144,13 @@ fun String.applySpans(representations: List<SpanRepresentation>): Editable {
|
||||||
return editable
|
return editable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createBoldSpan() =
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
StyleSpan(Typeface.BOLD, 700)
|
||||||
|
} else {
|
||||||
|
StyleSpan(Typeface.BOLD)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts or removes spans based on the selection range.
|
* Adjusts or removes spans based on the selection range.
|
||||||
*
|
*
|
||||||
|
|
|
@ -64,7 +64,10 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
if (preferences.biometricLock.value == BiometricLock.ENABLED) {
|
if (
|
||||||
|
preferences.biometricLock.value == BiometricLock.ENABLED &&
|
||||||
|
notallyXApplication.locked.value
|
||||||
|
) {
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,6 +237,7 @@ abstract class NotallyFragment : Fragment(), ItemListener {
|
||||||
maxLines.value,
|
maxLines.value,
|
||||||
maxTitle.value,
|
maxTitle.value,
|
||||||
labelTagsHiddenInOverview.value,
|
labelTagsHiddenInOverview.value,
|
||||||
|
imagesHiddenInOverview.value,
|
||||||
),
|
),
|
||||||
model.imageRoot,
|
model.imageRoot,
|
||||||
this@NotallyFragment,
|
this@NotallyFragment,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.view.View
|
||||||
import androidx.core.os.BundleCompat
|
import androidx.core.os.BundleCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
|
import com.philkes.notallyx.data.model.BaseNote
|
||||||
import com.philkes.notallyx.data.model.Folder
|
import com.philkes.notallyx.data.model.Folder
|
||||||
|
|
||||||
class SearchFragment : NotallyFragment() {
|
class SearchFragment : NotallyFragment() {
|
||||||
|
@ -44,6 +45,9 @@ class SearchFragment : NotallyFragment() {
|
||||||
isVisible = true
|
isVisible = true
|
||||||
}
|
}
|
||||||
} else binding?.ChipGroup?.isVisible = false
|
} else binding?.ChipGroup?.isVisible = false
|
||||||
|
getObservable().observe(viewLifecycleOwner) { items ->
|
||||||
|
model.actionMode.updateSelected(items?.filterIsInstance<BaseNote>()?.map { it.id })
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getBackground() = R.drawable.search
|
override fun getBackground() = R.drawable.search
|
||||||
|
|
|
@ -351,6 +351,17 @@ class SettingsFragment : Fragment() {
|
||||||
model.savePreference(labelTagsHiddenInOverview, enabled)
|
model.savePreference(labelTagsHiddenInOverview, enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
imagesHiddenInOverview.observe(viewLifecycleOwner) { value ->
|
||||||
|
binding.ImagesHiddenInOverview.setup(
|
||||||
|
imagesHiddenInOverview,
|
||||||
|
value,
|
||||||
|
requireContext(),
|
||||||
|
layoutInflater,
|
||||||
|
R.string.images_hidden_in_overview,
|
||||||
|
) { enabled ->
|
||||||
|
model.savePreference(imagesHiddenInOverview, enabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -188,16 +188,17 @@ abstract class EditActivity(private val type: Type) :
|
||||||
if (persistedId == null || notallyModel.originalNote == null) {
|
if (persistedId == null || notallyModel.originalNote == null) {
|
||||||
notallyModel.setState(id)
|
notallyModel.setState(id)
|
||||||
}
|
}
|
||||||
if (
|
if (notallyModel.isNewNote) {
|
||||||
notallyModel.isNewNote &&
|
when (intent.action) {
|
||||||
intent.action in setOf(Intent.ACTION_SEND, Intent.ACTION_SEND_MULTIPLE)
|
Intent.ACTION_SEND,
|
||||||
) {
|
Intent.ACTION_SEND_MULTIPLE -> handleSharedNote()
|
||||||
handleSharedNote()
|
Intent.ACTION_VIEW -> handleViewNote()
|
||||||
} else if (notallyModel.isNewNote) {
|
else ->
|
||||||
intent.getStringExtra(EXTRA_DISPLAYED_LABEL)?.let {
|
intent.getStringExtra(EXTRA_DISPLAYED_LABEL)?.let {
|
||||||
notallyModel.setLabels(listOf(it))
|
notallyModel.setLabels(listOf(it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupToolbars()
|
setupToolbars()
|
||||||
setupListeners()
|
setupListeners()
|
||||||
|
@ -632,6 +633,7 @@ abstract class EditActivity(private val type: Type) :
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun convertTo(type: Type) {
|
private fun convertTo(type: Type) {
|
||||||
|
updateModel()
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
notallyModel.convertTo(type)
|
notallyModel.convertTo(type)
|
||||||
val intent =
|
val intent =
|
||||||
|
@ -756,6 +758,26 @@ abstract class EditActivity(private val type: Type) :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleViewNote() {
|
||||||
|
val text =
|
||||||
|
intent.data?.let { uri ->
|
||||||
|
contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
|
inputStream.bufferedReader().readText()
|
||||||
|
}
|
||||||
|
?: run {
|
||||||
|
showToast(R.string.cant_load_file)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} ?: intent.getStringExtra(Intent.EXTRA_TEXT)
|
||||||
|
val title = intent.getStringExtra(Intent.EXTRA_SUBJECT)
|
||||||
|
if (text != null) {
|
||||||
|
notallyModel.body = Editable.Factory.getInstance().newEditable(text)
|
||||||
|
}
|
||||||
|
if (title != null) {
|
||||||
|
notallyModel.title = title
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresApi(24)
|
@RequiresApi(24)
|
||||||
override fun recordAudio() {
|
override fun recordAudio() {
|
||||||
val permission = Manifest.permission.RECORD_AUDIO
|
val permission = Manifest.permission.RECORD_AUDIO
|
||||||
|
|
|
@ -40,6 +40,7 @@ import com.philkes.notallyx.presentation.activity.note.PickNoteActivity.Companio
|
||||||
import com.philkes.notallyx.presentation.activity.note.PickNoteActivity.Companion.EXTRA_PICKED_NOTE_TYPE
|
import com.philkes.notallyx.presentation.activity.note.PickNoteActivity.Companion.EXTRA_PICKED_NOTE_TYPE
|
||||||
import com.philkes.notallyx.presentation.add
|
import com.philkes.notallyx.presentation.add
|
||||||
import com.philkes.notallyx.presentation.addIconButton
|
import com.philkes.notallyx.presentation.addIconButton
|
||||||
|
import com.philkes.notallyx.presentation.createBoldSpan
|
||||||
import com.philkes.notallyx.presentation.dp
|
import com.philkes.notallyx.presentation.dp
|
||||||
import com.philkes.notallyx.presentation.hideKeyboard
|
import com.philkes.notallyx.presentation.hideKeyboard
|
||||||
import com.philkes.notallyx.presentation.setControlsContrastColorForAllViews
|
import com.philkes.notallyx.presentation.setControlsContrastColorForAllViews
|
||||||
|
@ -212,7 +213,7 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
||||||
0,
|
0,
|
||||||
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
showAsAction = MenuItem.SHOW_AS_ACTION_NEVER,
|
||||||
) {
|
) {
|
||||||
binding.EnterBody.applySpan(StyleSpan(Typeface.BOLD))
|
binding.EnterBody.applySpan(createBoldSpan())
|
||||||
mode?.finish()
|
mode?.finish()
|
||||||
}
|
}
|
||||||
add(
|
add(
|
||||||
|
|
|
@ -52,6 +52,7 @@ open class PickNoteActivity : LockedActivity<ActivityPickNoteBinding>(), ItemLis
|
||||||
maxLines.value,
|
maxLines.value,
|
||||||
maxTitle.value,
|
maxTitle.value,
|
||||||
labelTagsHiddenInOverview.value,
|
labelTagsHiddenInOverview.value,
|
||||||
|
imagesHiddenInOverview.value,
|
||||||
),
|
),
|
||||||
application.getExternalImagesDirectory(),
|
application.getExternalImagesDirectory(),
|
||||||
this@PickNoteActivity,
|
this@PickNoteActivity,
|
||||||
|
|
|
@ -45,6 +45,7 @@ data class BaseNoteVHPreferences(
|
||||||
val maxLines: Int,
|
val maxLines: Int,
|
||||||
val maxTitleLines: Int,
|
val maxTitleLines: Int,
|
||||||
val hideLabels: Boolean,
|
val hideLabels: Boolean,
|
||||||
|
val hideImages: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
class BaseNoteVH(
|
class BaseNoteVH(
|
||||||
|
@ -209,9 +210,8 @@ class BaseNoteVH(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setImages(images: List<FileAttachment>, mediaRoot: File?) {
|
private fun setImages(images: List<FileAttachment>, mediaRoot: File?) {
|
||||||
|
|
||||||
binding.apply {
|
binding.apply {
|
||||||
if (images.isNotEmpty()) {
|
if (images.isNotEmpty() && !preferences.hideImages) {
|
||||||
ImageView.visibility = VISIBLE
|
ImageView.visibility = VISIBLE
|
||||||
Message.visibility = GONE
|
Message.visibility = GONE
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import android.text.style.TypefaceSpan
|
||||||
import android.text.style.URLSpan
|
import android.text.style.URLSpan
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
|
import com.philkes.notallyx.presentation.createBoldSpan
|
||||||
import com.philkes.notallyx.presentation.view.misc.StylableEditTextWithHistory
|
import com.philkes.notallyx.presentation.view.misc.StylableEditTextWithHistory
|
||||||
|
|
||||||
class TextFormattingAdapter(
|
class TextFormattingAdapter(
|
||||||
|
@ -35,7 +36,7 @@ class TextFormattingAdapter(
|
||||||
private val bold: Toggle =
|
private val bold: Toggle =
|
||||||
Toggle(R.string.bold, R.drawable.format_bold, false) {
|
Toggle(R.string.bold, R.drawable.format_bold, false) {
|
||||||
if (!it.checked) {
|
if (!it.checked) {
|
||||||
editText.applySpan(StyleSpan(Typeface.BOLD))
|
editText.applySpan(createBoldSpan())
|
||||||
} else {
|
} else {
|
||||||
editText.clearFormatting(type = StylableEditTextWithHistory.TextStyleType.BOLD)
|
editText.clearFormatting(type = StylableEditTextWithHistory.TextStyleType.BOLD)
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,6 +163,7 @@ class ListItemVH(
|
||||||
binding.Content.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
|
binding.Content.descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
|
||||||
}
|
}
|
||||||
setCanEdit(viewMode == NoteViewMode.EDIT)
|
setCanEdit(viewMode == NoteViewMode.EDIT)
|
||||||
|
isFocusable = !item.checked
|
||||||
when (viewMode) {
|
when (viewMode) {
|
||||||
NoteViewMode.EDIT -> {
|
NoteViewMode.EDIT -> {
|
||||||
setOnClickListener(null)
|
setOnClickListener(null)
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
@ -336,7 +335,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
fun importZipBackup(uri: Uri, password: String) {
|
fun importZipBackup(uri: Uri, password: String) {
|
||||||
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
app.log(TAG, throwable = throwable)
|
app.log(TAG, throwable = throwable)
|
||||||
app.showToast(R.string.invalid_backup)
|
app.showToast("${app.getString(R.string.invalid_backup)}: ${throwable.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val backupDir = app.getBackupDir()
|
val backupDir = app.getBackupDir()
|
||||||
|
@ -348,7 +347,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
fun importXmlBackup(uri: Uri) {
|
fun importXmlBackup(uri: Uri) {
|
||||||
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
app.log(TAG, throwable = throwable)
|
app.log(TAG, throwable = throwable)
|
||||||
app.showToast(R.string.invalid_backup)
|
app.showToast("${app.getString(R.string.invalid_backup)}: ${throwable.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch(exceptionHandler) {
|
viewModelScope.launch(exceptionHandler) {
|
||||||
|
@ -366,18 +365,15 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
|
||||||
|
|
||||||
fun importFromOtherApp(uri: Uri, importSource: ImportSource) {
|
fun importFromOtherApp(uri: Uri, importSource: ImportSource) {
|
||||||
val database = NotallyDatabase.getDatabase(app, observePreferences = false).value
|
val database = NotallyDatabase.getDatabase(app, observePreferences = false).value
|
||||||
|
|
||||||
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
Toast.makeText(
|
|
||||||
app,
|
|
||||||
if (throwable is ImportException) {
|
|
||||||
throwable.textResId
|
|
||||||
} else R.string.invalid_backup,
|
|
||||||
Toast.LENGTH_LONG,
|
|
||||||
)
|
|
||||||
.show()
|
|
||||||
app.log(TAG, throwable = throwable)
|
app.log(TAG, throwable = throwable)
|
||||||
|
if (throwable is ImportException) {
|
||||||
|
app.showToast(throwable.textResId)
|
||||||
|
} else {
|
||||||
|
app.showToast("${app.getString(R.string.invalid_backup)}: ${throwable.message}")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
viewModelScope.launch(exceptionHandler) {
|
viewModelScope.launch(exceptionHandler) {
|
||||||
val importedNotes =
|
val importedNotes =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
|
|
@ -84,6 +84,13 @@ class NotallyXPreferences private constructor(private val context: Context) {
|
||||||
false,
|
false,
|
||||||
R.string.labels_hidden_in_overview_title,
|
R.string.labels_hidden_in_overview_title,
|
||||||
)
|
)
|
||||||
|
val imagesHiddenInOverview =
|
||||||
|
BooleanPreference(
|
||||||
|
"imagesHiddenInOverview",
|
||||||
|
preferences,
|
||||||
|
false,
|
||||||
|
R.string.images_hidden_in_overview_title,
|
||||||
|
)
|
||||||
val maxLabels =
|
val maxLabels =
|
||||||
IntPreference(
|
IntPreference(
|
||||||
"maxLabelsInNavigation",
|
"maxLabelsInNavigation",
|
||||||
|
@ -233,6 +240,7 @@ class NotallyXPreferences private constructor(private val context: Context) {
|
||||||
backupPassword,
|
backupPassword,
|
||||||
backupOnSave,
|
backupOnSave,
|
||||||
autoSaveAfterIdleTime,
|
autoSaveAfterIdleTime,
|
||||||
|
imagesHiddenInOverview,
|
||||||
)
|
)
|
||||||
.forEach { it.refresh() }
|
.forEach { it.refresh() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import com.philkes.notallyx.NotallyXApplication
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
import com.philkes.notallyx.data.NotallyDatabase
|
import com.philkes.notallyx.data.NotallyDatabase
|
||||||
import com.philkes.notallyx.data.dao.BaseNoteDao
|
import com.philkes.notallyx.data.dao.BaseNoteDao
|
||||||
|
@ -42,9 +43,10 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
super.onReceive(context, intent)
|
super.onReceive(context, intent)
|
||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
ACTION_NOTES_MODIFIED -> {
|
ACTION_NOTES_MODIFIED -> {
|
||||||
|
val app = context.applicationContext as NotallyXApplication
|
||||||
val noteIds = intent.getLongArrayExtra(EXTRA_MODIFIED_NOTES)
|
val noteIds = intent.getLongArrayExtra(EXTRA_MODIFIED_NOTES)
|
||||||
if (noteIds != null) {
|
if (noteIds != null) {
|
||||||
updateWidgets(context, noteIds)
|
updateWidgets(context, noteIds, locked = app.locked.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ACTION_OPEN_NOTE -> openActivity(context, intent, EditNoteActivity::class.java)
|
ACTION_OPEN_NOTE -> openActivity(context, intent, EditNoteActivity::class.java)
|
||||||
|
@ -85,7 +87,8 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
baseNoteDao.updateChecked(noteId, childrenPositions + position, checked!!)
|
baseNoteDao.updateChecked(noteId, childrenPositions + position, checked!!)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
updateWidgets(context, longArrayOf(noteId))
|
val app = context.applicationContext as NotallyXApplication
|
||||||
|
updateWidgets(context, longArrayOf(noteId), locked = app.locked.value)
|
||||||
pendingResult.finish()
|
pendingResult.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,19 +138,19 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
appWidgetManager: AppWidgetManager,
|
appWidgetManager: AppWidgetManager,
|
||||||
appWidgetIds: IntArray,
|
appWidgetIds: IntArray,
|
||||||
) {
|
) {
|
||||||
val app = context.applicationContext as Application
|
val app = context.applicationContext as NotallyXApplication
|
||||||
val preferences = NotallyXPreferences.getInstance(app)
|
val preferences = NotallyXPreferences.getInstance(app)
|
||||||
|
|
||||||
appWidgetIds.forEach { id ->
|
appWidgetIds.forEach { id ->
|
||||||
val noteId = preferences.getWidgetData(id)
|
val noteId = preferences.getWidgetData(id)
|
||||||
val noteType = preferences.getWidgetNoteType(id) ?: return
|
val noteType = preferences.getWidgetNoteType(id) ?: return
|
||||||
updateWidget(app, appWidgetManager, id, noteId, noteType)
|
updateWidget(app, appWidgetManager, id, noteId, noteType, locked = app.locked.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun updateWidgets(context: Context, noteIds: LongArray? = null, locked: Boolean = false) {
|
fun updateWidgets(context: Context, noteIds: LongArray? = null, locked: Boolean) {
|
||||||
val app = context.applicationContext as Application
|
val app = context.applicationContext as Application
|
||||||
val preferences = NotallyXPreferences.getInstance(app)
|
val preferences = NotallyXPreferences.getInstance(app)
|
||||||
|
|
||||||
|
@ -181,13 +184,14 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
|
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
|
||||||
intent.embedIntentExtras()
|
intent.embedIntentExtras()
|
||||||
|
|
||||||
if (!locked) {
|
|
||||||
val database = NotallyDatabase.getDatabase(context).value
|
|
||||||
MainScope().launch {
|
MainScope().launch {
|
||||||
withContext(Dispatchers.IO) {
|
val database = NotallyDatabase.getDatabase(context).value
|
||||||
val color = database.getBaseNoteDao().getColorOfNote(noteId)
|
val color =
|
||||||
val preferences = NotallyXPreferences.getInstance(context)
|
withContext(Dispatchers.IO) { database.getBaseNoteDao().getColorOfNote(noteId) }
|
||||||
val (backgroundColor, _) = context.extractWidgetColors(color, preferences)
|
if (color == null) {
|
||||||
|
val app = context.applicationContext as Application
|
||||||
|
val preferences = NotallyXPreferences.getInstance(app)
|
||||||
|
preferences.deleteWidget(id)
|
||||||
val view =
|
val view =
|
||||||
RemoteViews(context.packageName, R.layout.widget).apply {
|
RemoteViews(context.packageName, R.layout.widget).apply {
|
||||||
setRemoteAdapter(R.id.ListView, intent)
|
setRemoteAdapter(R.id.ListView, intent)
|
||||||
|
@ -203,10 +207,36 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
)
|
)
|
||||||
setPendingIntentTemplate(
|
setPendingIntentTemplate(
|
||||||
R.id.ListView,
|
R.id.ListView,
|
||||||
|
Intent(context, WidgetProvider::class.java).asPendingIntent(context),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.updateAppWidget(id, view)
|
||||||
|
manager.notifyAppWidgetViewDataChanged(id, R.id.ListView)
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
if (!locked) {
|
||||||
|
val view =
|
||||||
|
RemoteViews(context.packageName, R.layout.widget).apply {
|
||||||
|
setRemoteAdapter(R.id.ListView, intent)
|
||||||
|
setEmptyView(R.id.ListView, R.id.Empty)
|
||||||
|
setOnClickPendingIntent(
|
||||||
|
R.id.Empty,
|
||||||
Intent(context, WidgetProvider::class.java)
|
Intent(context, WidgetProvider::class.java)
|
||||||
|
.apply {
|
||||||
|
action = ACTION_SELECT_NOTE
|
||||||
|
data = Uri.parse(toUri(Intent.URI_INTENT_SCHEME))
|
||||||
|
}
|
||||||
.asPendingIntent(context),
|
.asPendingIntent(context),
|
||||||
)
|
)
|
||||||
|
setPendingIntentTemplate(
|
||||||
|
R.id.ListView,
|
||||||
|
Intent(context, WidgetProvider::class.java).asPendingIntent(context),
|
||||||
|
)
|
||||||
|
|
||||||
|
val preferences = NotallyXPreferences.getInstance(context)
|
||||||
|
val (backgroundColor, _) =
|
||||||
|
context.extractWidgetColors(color, preferences)
|
||||||
noteType?.let {
|
noteType?.let {
|
||||||
setOnClickPendingIntent(
|
setOnClickPendingIntent(
|
||||||
R.id.Layout,
|
R.id.Layout,
|
||||||
|
@ -215,13 +245,10 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
.asPendingIntent(context),
|
.asPendingIntent(context),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
setInt(R.id.Layout, "setBackgroundColor", backgroundColor)
|
setInt(R.id.Layout, "setBackgroundColor", backgroundColor)
|
||||||
}
|
}
|
||||||
manager.updateAppWidget(id, view)
|
manager.updateAppWidget(id, view)
|
||||||
manager.notifyAppWidgetViewDataChanged(id, R.id.ListView)
|
manager.notifyAppWidgetViewDataChanged(id, R.id.ListView)
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
val view =
|
val view =
|
||||||
RemoteViews(context.packageName, R.layout.widget_locked).apply {
|
RemoteViews(context.packageName, R.layout.widget_locked).apply {
|
||||||
|
@ -242,6 +269,7 @@ class WidgetProvider : AppWidgetProvider() {
|
||||||
manager.updateAppWidget(id, view)
|
manager.updateAppWidget(id, view)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getWidgetOpenNoteIntent(noteType: Type, noteId: Long): Intent {
|
fun getWidgetOpenNoteIntent(noteType: Type, noteId: Long): Intent {
|
||||||
return Intent().setOpenNoteIntent(noteType, noteId)
|
return Intent().setOpenNoteIntent(noteType, noteId)
|
||||||
|
|
|
@ -44,6 +44,13 @@ class ActionMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateSelected(availableItemIds: List<Long>?) {
|
||||||
|
selectedNotes.keys
|
||||||
|
.filter { availableItemIds?.contains(it) == false }
|
||||||
|
.forEach { selectedNotes.remove(it) }
|
||||||
|
refresh()
|
||||||
|
}
|
||||||
|
|
||||||
fun isEnabled() = enabled.value
|
fun isEnabled() = enabled.value
|
||||||
|
|
||||||
// We assume selectedNotes.size is 1
|
// We assume selectedNotes.size is 1
|
||||||
|
|
|
@ -80,7 +80,7 @@ private fun Context.createReminderAlarmIntent(noteId: Long, reminderId: Long): P
|
||||||
intent.putExtra(ReminderReceiver.EXTRA_NOTE_ID, noteId)
|
intent.putExtra(ReminderReceiver.EXTRA_NOTE_ID, noteId)
|
||||||
return PendingIntent.getBroadcast(
|
return PendingIntent.getBroadcast(
|
||||||
this,
|
this,
|
||||||
0,
|
(noteId.toString() + reminderId.toString()).toInt(),
|
||||||
intent,
|
intent,
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE,
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package com.philkes.notallyx.utils
|
package com.philkes.notallyx.utils
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.KeyguardManager
|
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
@ -12,7 +11,6 @@ import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.hardware.biometrics.BiometricManager
|
import android.hardware.biometrics.BiometricManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -47,7 +45,6 @@ import java.io.OutputStreamWriter
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
import java.lang.UnsupportedOperationException
|
import java.lang.UnsupportedOperationException
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
@ -126,30 +123,15 @@ fun Context.getFileName(uri: Uri): String? =
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.canAuthenticateWithBiometrics(): Int {
|
fun Context.canAuthenticateWithBiometrics(): Int {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
val biometricManager = androidx.biometric.BiometricManager.from(this)
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
val authenticators =
|
||||||
val keyguardManager = ContextCompat.getSystemService(this, KeyguardManager::class.java)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
val packageManager: PackageManager = this.packageManager
|
androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||||
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
|
androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
|
||||||
}
|
|
||||||
if (keyguardManager?.isKeyguardSecure == false) {
|
|
||||||
return BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED
|
|
||||||
}
|
|
||||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
|
||||||
val biometricManager: BiometricManager =
|
|
||||||
this.getSystemService(BiometricManager::class.java)
|
|
||||||
return biometricManager.canAuthenticate()
|
|
||||||
} else {
|
} else {
|
||||||
val biometricManager: BiometricManager =
|
androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
this.getSystemService(BiometricManager::class.java)
|
|
||||||
return biometricManager.canAuthenticate(
|
|
||||||
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
|
||||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
return biometricManager.canAuthenticate(authenticators)
|
||||||
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.getUriForFile(file: File): Uri =
|
fun Context.getUriForFile(file: File): Uri =
|
||||||
|
@ -173,8 +155,7 @@ fun ContextWrapper.log(
|
||||||
fun ContextWrapper.getLastExceptionLog(): String? {
|
fun ContextWrapper.getLastExceptionLog(): String? {
|
||||||
val logFile = getLogFile()
|
val logFile = getLogFile()
|
||||||
if (logFile.exists()) {
|
if (logFile.exists()) {
|
||||||
val logContents = logFile.readText().substringAfterLast("[Start]")
|
return logFile.readText().substringAfterLast("[Start]")
|
||||||
return URLEncoder.encode(logContents, StandardCharsets.UTF_8.toString())
|
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -204,10 +185,10 @@ fun Context.logToFile(
|
||||||
val logFile =
|
val logFile =
|
||||||
folder.findFile(fileName).let {
|
folder.findFile(fileName).let {
|
||||||
if (it == null || !it.exists()) {
|
if (it == null || !it.exists()) {
|
||||||
folder.createFile("text/plain", fileName)
|
folder.createFile("text/plain", fileName.removeSuffix(".txt"))
|
||||||
} else if (it.isLargerThanKb(MAX_LOGS_FILE_SIZE_KB)) {
|
} else if (it.isLargerThanKb(MAX_LOGS_FILE_SIZE_KB)) {
|
||||||
it.delete()
|
it.delete()
|
||||||
folder.createFile("text/plain", fileName)
|
folder.createFile("text/plain", fileName.removeSuffix(".txt"))
|
||||||
} else it
|
} else it
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,7 @@ fun Context.getExportedPath() = getEmptyFolder("exported")
|
||||||
|
|
||||||
fun ContextWrapper.getLogsDir() = File(filesDir, "logs").also { it.mkdir() }
|
fun ContextWrapper.getLogsDir() = File(filesDir, "logs").also { it.mkdir() }
|
||||||
|
|
||||||
const val APP_LOG_FILE_NAME = "Log.v1.txt"
|
const val APP_LOG_FILE_NAME = "notallyx-logs.txt"
|
||||||
|
|
||||||
fun ContextWrapper.getLogFile(): File {
|
fun ContextWrapper.getLogFile(): File {
|
||||||
return File(getLogsDir(), APP_LOG_FILE_NAME)
|
return File(getLogsDir(), APP_LOG_FILE_NAME)
|
||||||
|
|
|
@ -89,7 +89,7 @@ import net.lingala.zip4j.model.enums.EncryptionMethod
|
||||||
private const val TAG = "ExportExtensions"
|
private const val TAG = "ExportExtensions"
|
||||||
private const val NOTIFICATION_CHANNEL_ID = "AutoBackups"
|
private const val NOTIFICATION_CHANNEL_ID = "AutoBackups"
|
||||||
private const val NOTIFICATION_ID = 123412
|
private const val NOTIFICATION_ID = 123412
|
||||||
private const val NOTALLYX_BACKUP_LOGS_FILE = "notallyx-backup-logs"
|
private const val NOTALLYX_BACKUP_LOGS_FILE = "notallyx-backup-logs.txt"
|
||||||
private const val OUTPUT_DATA_BACKUP_URI = "backupUri"
|
private const val OUTPUT_DATA_BACKUP_URI = "backupUri"
|
||||||
|
|
||||||
const val AUTO_BACKUP_WORK_NAME = "com.philkes.notallyx.AutoBackupWork"
|
const val AUTO_BACKUP_WORK_NAME = "com.philkes.notallyx.AutoBackupWork"
|
||||||
|
@ -174,7 +174,7 @@ fun ContextWrapper.autoBackupOnSave(backupPath: String, password: String, savedN
|
||||||
backupFile = folder.createFile(MIME_TYPE_ZIP, ON_SAVE_BACKUP_FILE)
|
backupFile = folder.createFile(MIME_TYPE_ZIP, ON_SAVE_BACKUP_FILE)
|
||||||
exportAsZip(backupFile!!.uri, password = password)
|
exportAsZip(backupFile!!.uri, password = password)
|
||||||
} else {
|
} else {
|
||||||
NotallyDatabase.getDatabase(this, observePreferences = false).value.checkpoint()
|
val (_, file) = copyDatabase()
|
||||||
val files =
|
val files =
|
||||||
with(savedNote) {
|
with(savedNote) {
|
||||||
images.map {
|
images.map {
|
||||||
|
@ -192,10 +192,7 @@ fun ContextWrapper.autoBackupOnSave(backupPath: String, password: String, savedN
|
||||||
audios.map {
|
audios.map {
|
||||||
BackupFile(SUBFOLDER_AUDIOS, File(getExternalAudioDirectory(), it.name))
|
BackupFile(SUBFOLDER_AUDIOS, File(getExternalAudioDirectory(), it.name))
|
||||||
} +
|
} +
|
||||||
BackupFile(
|
BackupFile(null, file)
|
||||||
null,
|
|
||||||
NotallyDatabase.getCurrentDatabaseFile(this@autoBackupOnSave),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
exportToZip(backupFile.uri, files, password)
|
exportToZip(backupFile.uri, files, password)
|
||||||
|
@ -417,7 +414,7 @@ fun ContextWrapper.copyDatabase(): Pair<NotallyDatabase, File> {
|
||||||
val cipher = getInitializedCipherForDecryption(iv = preferences.iv.value!!)
|
val cipher = getInitializedCipherForDecryption(iv = preferences.iv.value!!)
|
||||||
val passphrase = cipher.doFinal(preferences.databaseEncryptionKey.value)
|
val passphrase = cipher.doFinal(preferences.databaseEncryptionKey.value)
|
||||||
val decryptedFile = File(cacheDir, DATABASE_NAME)
|
val decryptedFile = File(cacheDir, DATABASE_NAME)
|
||||||
decryptDatabase(this, passphrase, decryptedFile, databaseFile)
|
decryptDatabase(this, passphrase, databaseFile, decryptedFile)
|
||||||
Pair(database, decryptedFile)
|
Pair(database, decryptedFile)
|
||||||
} else {
|
} else {
|
||||||
val dbFile = File(cacheDir, DATABASE_NAME)
|
val dbFile = File(cacheDir, DATABASE_NAME)
|
||||||
|
@ -517,13 +514,14 @@ fun exportPdfFile(
|
||||||
total: Int? = null,
|
total: Int? = null,
|
||||||
duplicateFileCount: Int = 1,
|
duplicateFileCount: Int = 1,
|
||||||
) {
|
) {
|
||||||
val filePath = "$fileName.${ExportMimeType.PDF.fileExtension}"
|
val validFileName = fileName.ifBlank { app.getString(R.string.note) }
|
||||||
|
val filePath = "$validFileName.${ExportMimeType.PDF.fileExtension}"
|
||||||
if (folder.findFile(filePath)?.exists() == true) {
|
if (folder.findFile(filePath)?.exists() == true) {
|
||||||
return exportPdfFile(
|
return exportPdfFile(
|
||||||
app,
|
app,
|
||||||
note,
|
note,
|
||||||
folder,
|
folder,
|
||||||
"${fileName.removeTrailingParentheses()} ($duplicateFileCount)",
|
"${validFileName.removeTrailingParentheses()} ($duplicateFileCount)",
|
||||||
pdfPrintListener,
|
pdfPrintListener,
|
||||||
progress,
|
progress,
|
||||||
counter,
|
counter,
|
||||||
|
|
|
@ -28,6 +28,7 @@ import com.philkes.notallyx.data.model.parseToColorString
|
||||||
import com.philkes.notallyx.presentation.getQuantityString
|
import com.philkes.notallyx.presentation.getQuantityString
|
||||||
import com.philkes.notallyx.presentation.showToast
|
import com.philkes.notallyx.presentation.showToast
|
||||||
import com.philkes.notallyx.presentation.viewmodel.NotallyModel.FileType
|
import com.philkes.notallyx.presentation.viewmodel.NotallyModel.FileType
|
||||||
|
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
|
||||||
import com.philkes.notallyx.utils.FileError
|
import com.philkes.notallyx.utils.FileError
|
||||||
import com.philkes.notallyx.utils.SUBFOLDER_AUDIOS
|
import com.philkes.notallyx.utils.SUBFOLDER_AUDIOS
|
||||||
import com.philkes.notallyx.utils.SUBFOLDER_FILES
|
import com.philkes.notallyx.utils.SUBFOLDER_FILES
|
||||||
|
@ -44,6 +45,8 @@ import com.philkes.notallyx.utils.log
|
||||||
import com.philkes.notallyx.utils.mimeTypeToFileExtension
|
import com.philkes.notallyx.utils.mimeTypeToFileExtension
|
||||||
import com.philkes.notallyx.utils.rename
|
import com.philkes.notallyx.utils.rename
|
||||||
import com.philkes.notallyx.utils.scheduleNoteReminders
|
import com.philkes.notallyx.utils.scheduleNoteReminders
|
||||||
|
import com.philkes.notallyx.utils.security.SQLCipherUtils
|
||||||
|
import com.philkes.notallyx.utils.security.decryptDatabase
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
@ -86,12 +89,31 @@ suspend fun ContextWrapper.importZip(
|
||||||
NotallyDatabase.DATABASE_NAME,
|
NotallyDatabase.DATABASE_NAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
val database =
|
var dbFile = File(databaseFolder, NotallyDatabase.DATABASE_NAME)
|
||||||
SQLiteDatabase.openDatabase(
|
val state = SQLCipherUtils.getDatabaseState(dbFile)
|
||||||
File(databaseFolder, NotallyDatabase.DATABASE_NAME).path,
|
if (state == SQLCipherUtils.State.ENCRYPTED) {
|
||||||
null,
|
val fallbackEncryptionKey =
|
||||||
SQLiteDatabase.OPEN_READONLY,
|
NotallyXPreferences.getInstance(this@importZip)
|
||||||
|
.fallbackDatabaseEncryptionKey
|
||||||
|
.value
|
||||||
|
if (fallbackEncryptionKey != null) {
|
||||||
|
val dbFileDecrypted =
|
||||||
|
File(databaseFolder, "${NotallyDatabase.DATABASE_NAME}-decrypted")
|
||||||
|
decryptDatabase(
|
||||||
|
this@importZip,
|
||||||
|
fallbackEncryptionKey,
|
||||||
|
dbFile,
|
||||||
|
dbFileDecrypted,
|
||||||
)
|
)
|
||||||
|
dbFile = dbFileDecrypted
|
||||||
|
} else {
|
||||||
|
throw IllegalArgumentException(
|
||||||
|
"Backup contains encrypted database and 'fallbackDatabaseEncryptionKey' has no value!"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val database =
|
||||||
|
SQLiteDatabase.openDatabase(dbFile.path, null, SQLiteDatabase.OPEN_READONLY)
|
||||||
|
|
||||||
val labelCursor = database.query("Label", null, null, null, null, null, null)
|
val labelCursor = database.query("Label", null, null, null, null, null, null)
|
||||||
val baseNoteCursor = database.query("BaseNote", null, null, null, null, null, null)
|
val baseNoteCursor = database.query("BaseNote", null, null, null, null, null, null)
|
||||||
|
@ -177,14 +199,11 @@ suspend fun ContextWrapper.importZip(
|
||||||
showToast(message)
|
showToast(message)
|
||||||
} catch (e: ZipException) {
|
} catch (e: ZipException) {
|
||||||
if (e.type == ZipException.Type.WRONG_PASSWORD) {
|
if (e.type == ZipException.Type.WRONG_PASSWORD) {
|
||||||
|
log(TAG, throwable = e)
|
||||||
showToast(R.string.wrong_password)
|
showToast(R.string.wrong_password)
|
||||||
} else {
|
} else {
|
||||||
log(TAG, throwable = e)
|
throw e
|
||||||
showToast(R.string.invalid_backup)
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
showToast(R.string.invalid_backup)
|
|
||||||
log(TAG, throwable = e)
|
|
||||||
} finally {
|
} finally {
|
||||||
importingBackup?.value = ImportProgress(inProgress = false)
|
importingBackup?.value = ImportProgress(inProgress = false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,8 @@ fun decryptDatabase(context: ContextWrapper, passphrase: ByteArray) {
|
||||||
fun decryptDatabase(
|
fun decryptDatabase(
|
||||||
context: Context,
|
context: Context,
|
||||||
passphrase: ByteArray,
|
passphrase: ByteArray,
|
||||||
decryptedFile: File,
|
|
||||||
databaseFile: File,
|
databaseFile: File,
|
||||||
|
decryptedFile: File,
|
||||||
) {
|
) {
|
||||||
val state = SQLCipherUtils.getDatabaseState(databaseFile)
|
val state = SQLCipherUtils.getDatabaseState(databaseFile)
|
||||||
if (state == SQLCipherUtils.State.ENCRYPTED) {
|
if (state == SQLCipherUtils.State.ENCRYPTED) {
|
||||||
|
|
|
@ -4,15 +4,13 @@ import android.app.Activity
|
||||||
import android.app.KeyguardManager
|
import android.app.KeyguardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.hardware.biometrics.BiometricManager
|
|
||||||
import android.hardware.biometrics.BiometricPrompt
|
|
||||||
import android.hardware.fingerprint.FingerprintManager
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.CancellationSignal
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.philkes.notallyx.R
|
import com.philkes.notallyx.R
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
|
|
||||||
|
@ -27,7 +25,7 @@ fun Activity.showBiometricOrPinPrompt(
|
||||||
) {
|
) {
|
||||||
showBiometricOrPinPrompt(
|
showBiometricOrPinPrompt(
|
||||||
isForDecrypt,
|
isForDecrypt,
|
||||||
this,
|
this as FragmentActivity,
|
||||||
activityResultLauncher,
|
activityResultLauncher,
|
||||||
titleResId,
|
titleResId,
|
||||||
descriptionResId,
|
descriptionResId,
|
||||||
|
@ -48,7 +46,7 @@ fun Fragment.showBiometricOrPinPrompt(
|
||||||
) {
|
) {
|
||||||
showBiometricOrPinPrompt(
|
showBiometricOrPinPrompt(
|
||||||
isForDecrypt,
|
isForDecrypt,
|
||||||
requireContext(),
|
activity!!,
|
||||||
activityResultLauncher,
|
activityResultLauncher,
|
||||||
titleResId,
|
titleResId,
|
||||||
descriptionResId,
|
descriptionResId,
|
||||||
|
@ -60,7 +58,7 @@ fun Fragment.showBiometricOrPinPrompt(
|
||||||
|
|
||||||
private fun showBiometricOrPinPrompt(
|
private fun showBiometricOrPinPrompt(
|
||||||
isForDecrypt: Boolean,
|
isForDecrypt: Boolean,
|
||||||
context: Context,
|
context: FragmentActivity,
|
||||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||||
titleResId: Int,
|
titleResId: Int,
|
||||||
descriptionResId: Int? = null,
|
descriptionResId: Int? = null,
|
||||||
|
@ -69,19 +67,25 @@ private fun showBiometricOrPinPrompt(
|
||||||
onFailure: (errorCode: Int?) -> Unit,
|
onFailure: (errorCode: Int?) -> Unit,
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
|
||||||
// Android 11+ with BiometricPrompt and Authenticators
|
val promptInfo =
|
||||||
val prompt =
|
BiometricPrompt.PromptInfo.Builder()
|
||||||
BiometricPrompt.Builder(context)
|
|
||||||
.apply {
|
.apply {
|
||||||
setTitle(context.getString(titleResId))
|
setTitle(context.getString(titleResId))
|
||||||
descriptionResId?.let {
|
descriptionResId?.let {
|
||||||
setDescription(context.getString(descriptionResId))
|
setDescription(context.getString(descriptionResId))
|
||||||
}
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
setAllowedAuthenticators(
|
setAllowedAuthenticators(
|
||||||
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
setNegativeButtonText(context.getString(R.string.cancel))
|
||||||
|
setAllowedAuthenticators(
|
||||||
|
BiometricManager.Authenticators.BIOMETRIC_STRONG
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
val cipher =
|
val cipher =
|
||||||
|
@ -90,16 +94,13 @@ private fun showBiometricOrPinPrompt(
|
||||||
} else {
|
} else {
|
||||||
getInitializedCipherForEncryption()
|
getInitializedCipherForEncryption()
|
||||||
}
|
}
|
||||||
prompt.authenticate(
|
val authCallback =
|
||||||
BiometricPrompt.CryptoObject(cipher),
|
|
||||||
getCancellationSignal(context),
|
|
||||||
context.mainExecutor,
|
|
||||||
object : BiometricPrompt.AuthenticationCallback() {
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
override fun onAuthenticationSucceeded(
|
override fun onAuthenticationSucceeded(
|
||||||
result: BiometricPrompt.AuthenticationResult?
|
result: BiometricPrompt.AuthenticationResult
|
||||||
) {
|
) {
|
||||||
super.onAuthenticationSucceeded(result)
|
super.onAuthenticationSucceeded(result)
|
||||||
onSuccess.invoke(result!!.cryptoObject!!.cipher)
|
onSuccess.invoke(result.cryptoObject!!.cipher!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
override fun onAuthenticationFailed() {
|
||||||
|
@ -107,104 +108,14 @@ private fun showBiometricOrPinPrompt(
|
||||||
onFailure.invoke(null)
|
onFailure.invoke(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
|
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||||
super.onAuthenticationError(errorCode, errString)
|
super.onAuthenticationError(errorCode, errString)
|
||||||
onFailure.invoke(errorCode)
|
onFailure.invoke(errorCode)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Build.VERSION.SDK_INT == Build.VERSION_CODES.Q -> {
|
|
||||||
// Android 10: Use BiometricPrompt without Authenticators
|
|
||||||
val prompt =
|
val prompt =
|
||||||
BiometricPrompt.Builder(context)
|
BiometricPrompt(context, ContextCompat.getMainExecutor(context), authCallback)
|
||||||
.apply {
|
prompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))
|
||||||
setTitle(context.getString(titleResId))
|
|
||||||
descriptionResId?.let {
|
|
||||||
setDescription(context.getString(descriptionResId))
|
|
||||||
}
|
|
||||||
setNegativeButton(
|
|
||||||
context.getString(R.string.cancel),
|
|
||||||
context.mainExecutor,
|
|
||||||
) { _, _ ->
|
|
||||||
onFailure.invoke(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.build()
|
|
||||||
val cipher =
|
|
||||||
if (isForDecrypt) {
|
|
||||||
getInitializedCipherForDecryption(iv = cipherIv!!)
|
|
||||||
} else {
|
|
||||||
getInitializedCipherForEncryption()
|
|
||||||
}
|
|
||||||
prompt.authenticate(
|
|
||||||
BiometricPrompt.CryptoObject(cipher),
|
|
||||||
getCancellationSignal(context),
|
|
||||||
context.mainExecutor,
|
|
||||||
object : BiometricPrompt.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationSucceeded(
|
|
||||||
result: BiometricPrompt.AuthenticationResult?
|
|
||||||
) {
|
|
||||||
super.onAuthenticationSucceeded(result)
|
|
||||||
onSuccess.invoke(result!!.cryptoObject!!.cipher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
|
||||||
super.onAuthenticationFailed()
|
|
||||||
onFailure.invoke(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
|
|
||||||
super.onAuthenticationError(errorCode, errString)
|
|
||||||
onFailure.invoke(errorCode)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
|
|
||||||
val fingerprintManager =
|
|
||||||
ContextCompat.getSystemService(context, FingerprintManager::class.java)
|
|
||||||
if (
|
|
||||||
fingerprintManager?.isHardwareDetected == true &&
|
|
||||||
fingerprintManager.hasEnrolledFingerprints()
|
|
||||||
) {
|
|
||||||
val cipher =
|
|
||||||
if (isForDecrypt) {
|
|
||||||
getInitializedCipherForDecryption(iv = cipherIv!!)
|
|
||||||
} else {
|
|
||||||
getInitializedCipherForEncryption()
|
|
||||||
}
|
|
||||||
fingerprintManager.authenticate(
|
|
||||||
FingerprintManager.CryptoObject(cipher),
|
|
||||||
getCancellationSignal(context),
|
|
||||||
0,
|
|
||||||
object : FingerprintManager.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationSucceeded(
|
|
||||||
result: FingerprintManager.AuthenticationResult?
|
|
||||||
) {
|
|
||||||
super.onAuthenticationSucceeded(result)
|
|
||||||
onSuccess.invoke(result!!.cryptoObject!!.cipher)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationFailed() {
|
|
||||||
super.onAuthenticationFailed()
|
|
||||||
onFailure.invoke(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationError(
|
|
||||||
errorCode: Int,
|
|
||||||
errString: CharSequence?,
|
|
||||||
) {
|
|
||||||
super.onAuthenticationError(errorCode, errString)
|
|
||||||
onFailure.invoke(errorCode)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
promptPinAuthentication(context, activityResultLauncher, titleResId, onFailure)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -214,14 +125,6 @@ private fun showBiometricOrPinPrompt(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCancellationSignal(context: Context): CancellationSignal {
|
|
||||||
return CancellationSignal().apply {
|
|
||||||
setOnCancelListener {
|
|
||||||
Toast.makeText(context, R.string.biometrics_failure, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun promptPinAuthentication(
|
private fun promptPinAuthentication(
|
||||||
context: Context,
|
context: Context,
|
||||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||||
|
|
|
@ -88,6 +88,10 @@
|
||||||
android:id="@+id/LabelsHiddenInOverview"
|
android:id="@+id/LabelsHiddenInOverview"
|
||||||
layout="@layout/preference" />
|
layout="@layout/preference" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/ImagesHiddenInOverview"
|
||||||
|
layout="@layout/preference" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
|
|
|
@ -123,6 +123,7 @@
|
||||||
<string name="disable_lock_description">Toto rovněž dešifruje databázi</string>
|
<string name="disable_lock_description">Toto rovněž dešifruje databázi</string>
|
||||||
<string name="disable_lock_title">Vypnout zámek pomocí biometrických údajů/PIN</string>
|
<string name="disable_lock_title">Vypnout zámek pomocí biometrických údajů/PIN</string>
|
||||||
<string name="disabled">Zakázat</string>
|
<string name="disabled">Zakázat</string>
|
||||||
|
<string name="disallow_screenshots">Zakázat pořizování snímků obrazovky</string>
|
||||||
<string name="discard">Zahodit</string>
|
<string name="discard">Zahodit</string>
|
||||||
<string name="display_text">Text k zobrazení</string>
|
<string name="display_text">Text k zobrazení</string>
|
||||||
<string name="donate">Podpořit</string>
|
<string name="donate">Podpořit</string>
|
||||||
|
@ -162,6 +163,8 @@
|
||||||
<string name="help">Nápověda</string>
|
<string name="help">Nápověda</string>
|
||||||
<string name="hours">Hodiny</string>
|
<string name="hours">Hodiny</string>
|
||||||
<string name="image_format_not_supported">Formát obrázku není podporován</string>
|
<string name="image_format_not_supported">Formát obrázku není podporován</string>
|
||||||
|
<string name="images_hidden_in_overview">Pokud tuto funkci povolíte, obrázky poznámek budou v přehledu skryty.</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Skrýt obrázky v přehledu</string>
|
||||||
<string name="import_action">Importovat</string>
|
<string name="import_action">Importovat</string>
|
||||||
<string name="import_backup">Importovat zálohu</string>
|
<string name="import_backup">Importovat zálohu</string>
|
||||||
<string name="import_backup_password_hint">Pokud vaše záloha není chráněna heslem, jednoduše stiskněte tlačítko Importovat, jinak zadejte správné heslo.</string>
|
<string name="import_backup_password_hint">Pokud vaše záloha není chráněna heslem, jednoduše stiskněte tlačítko Importovat, jinak zadejte správné heslo.</string>
|
||||||
|
|
|
@ -159,6 +159,8 @@
|
||||||
<string name="help">Hilfe</string>
|
<string name="help">Hilfe</string>
|
||||||
<string name="hours">Stunden</string>
|
<string name="hours">Stunden</string>
|
||||||
<string name="image_format_not_supported">Bildformat nicht unterstützt</string>
|
<string name="image_format_not_supported">Bildformat nicht unterstützt</string>
|
||||||
|
<string name="images_hidden_in_overview">Ist dies aktiviert, werden die Bilder der Notizen in der Übersicht ausgeblendet</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Verberge Bilder in Übersicht</string>
|
||||||
<string name="import_action">Import</string>
|
<string name="import_action">Import</string>
|
||||||
<string name="import_backup">Backup importieren</string>
|
<string name="import_backup">Backup importieren</string>
|
||||||
<string name="import_backup_password_hint">Falls das Backup nicht passwortgeschützt ist, drücken Sie einfach auf Importieren, andernfalls geben Sie das richtige Passwort ein.</string>
|
<string name="import_backup_password_hint">Falls das Backup nicht passwortgeschützt ist, drücken Sie einfach auf Importieren, andernfalls geben Sie das richtige Passwort ein.</string>
|
||||||
|
|
|
@ -119,6 +119,7 @@
|
||||||
<string name="disable_lock_description">Esto también descifrará la base de datos</string>
|
<string name="disable_lock_description">Esto también descifrará la base de datos</string>
|
||||||
<string name="disable_lock_title">Deshabilitar bloqueo biométrico/PIN</string>
|
<string name="disable_lock_title">Deshabilitar bloqueo biométrico/PIN</string>
|
||||||
<string name="disabled">Deshabilitado</string>
|
<string name="disabled">Deshabilitado</string>
|
||||||
|
<string name="disallow_screenshots">No permitir capturas de pantalla</string>
|
||||||
<string name="discard">Descartar</string>
|
<string name="discard">Descartar</string>
|
||||||
<string name="display_text">Texto a pantalla</string>
|
<string name="display_text">Texto a pantalla</string>
|
||||||
<string name="donate">Hacer donación</string>
|
<string name="donate">Hacer donación</string>
|
||||||
|
@ -158,6 +159,8 @@
|
||||||
<string name="help">Ayuda</string>
|
<string name="help">Ayuda</string>
|
||||||
<string name="hours">Horas</string>
|
<string name="hours">Horas</string>
|
||||||
<string name="image_format_not_supported">Formato de imagen no soportado</string>
|
<string name="image_format_not_supported">Formato de imagen no soportado</string>
|
||||||
|
<string name="images_hidden_in_overview">Al habilitar esta opción, se ocultarán las imágenes de notas de la descripción general.</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Ocultar imágenes en vista general</string>
|
||||||
<string name="import_action">Importar</string>
|
<string name="import_action">Importar</string>
|
||||||
<string name="import_backup">Importar copia de seguridad</string>
|
<string name="import_backup">Importar copia de seguridad</string>
|
||||||
<string name="import_backup_password_hint">Si su copia de seguridad no está protegida con contraseña, simplemente presione Importar, de lo contrario ingrese la contraseña correcta.</string>
|
<string name="import_backup_password_hint">Si su copia de seguridad no está protegida con contraseña, simplemente presione Importar, de lo contrario ingrese la contraseña correcta.</string>
|
||||||
|
|
|
@ -119,6 +119,7 @@
|
||||||
<string name="disable_lock_description">La base de donnée sera aussi décryptée</string>
|
<string name="disable_lock_description">La base de donnée sera aussi décryptée</string>
|
||||||
<string name="disable_lock_title">Désactiver le verrouillage biométrique/code PIN</string>
|
<string name="disable_lock_title">Désactiver le verrouillage biométrique/code PIN</string>
|
||||||
<string name="disabled">Désactivé</string>
|
<string name="disabled">Désactivé</string>
|
||||||
|
<string name="disallow_screenshots">Bloquer les captures d’écran</string>
|
||||||
<string name="discard">Supprimer</string>
|
<string name="discard">Supprimer</string>
|
||||||
<string name="display_text">Texte à afficher</string>
|
<string name="display_text">Texte à afficher</string>
|
||||||
<string name="donate">Faire un don</string>
|
<string name="donate">Faire un don</string>
|
||||||
|
@ -158,6 +159,8 @@
|
||||||
<string name="help">Aide</string>
|
<string name="help">Aide</string>
|
||||||
<string name="hours">Heures</string>
|
<string name="hours">Heures</string>
|
||||||
<string name="image_format_not_supported">Format d\'image non supporté</string>
|
<string name="image_format_not_supported">Format d\'image non supporté</string>
|
||||||
|
<string name="images_hidden_in_overview">En activant cette option, les images des notes seront masquées dans l\'aperçu</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Masquer les images dans la vue d\'ensemble</string>
|
||||||
<string name="import_action">Importer</string>
|
<string name="import_action">Importer</string>
|
||||||
<string name="import_backup">Importer une sauvegarde</string>
|
<string name="import_backup">Importer une sauvegarde</string>
|
||||||
<string name="import_backup_password_hint">Si votre sauvegarde n\'est pas protégée par mot de passe, cliquez seulement sur \"Importer une sauvegarde\", sinon entrez le mot de passe correspondant.</string>
|
<string name="import_backup_password_hint">Si votre sauvegarde n\'est pas protégée par mot de passe, cliquez seulement sur \"Importer une sauvegarde\", sinon entrez le mot de passe correspondant.</string>
|
||||||
|
|
|
@ -153,6 +153,8 @@
|
||||||
<string name="help">Aiuto</string>
|
<string name="help">Aiuto</string>
|
||||||
<string name="hours">Ore</string>
|
<string name="hours">Ore</string>
|
||||||
<string name="image_format_not_supported">Formato immagine non supportato</string>
|
<string name="image_format_not_supported">Formato immagine non supportato</string>
|
||||||
|
<string name="images_hidden_in_overview">Abilitando questa opzione le immagini delle note verranno nascoste nella panoramica.</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Nascondi immagini nella panoramica</string>
|
||||||
<string name="import_action">Importa</string>
|
<string name="import_action">Importa</string>
|
||||||
<string name="import_backup">Importa backup</string>
|
<string name="import_backup">Importa backup</string>
|
||||||
<string name="import_backup_password_hint">Se il tuo backup non è protetto da password premi semplicemente Importa, altrimenti inserisci la password corretta.</string>
|
<string name="import_backup_password_hint">Se il tuo backup non è protetto da password premi semplicemente Importa, altrimenti inserisci la password corretta.</string>
|
||||||
|
|
|
@ -166,6 +166,8 @@
|
||||||
<string name="help">Pomoc</string>
|
<string name="help">Pomoc</string>
|
||||||
<string name="hours">Godzin</string>
|
<string name="hours">Godzin</string>
|
||||||
<string name="image_format_not_supported">Format obrazu nie jest obsługiwany</string>
|
<string name="image_format_not_supported">Format obrazu nie jest obsługiwany</string>
|
||||||
|
<string name="images_hidden_in_overview">Po włączeniu tej opcji obrazy notatek będą ukryte w przeglądzie</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Ukryj obrazy w przeglądzie</string>
|
||||||
<string name="import_action">Przywracanie</string>
|
<string name="import_action">Przywracanie</string>
|
||||||
<string name="import_backup">Przywróć kopię zapasową</string>
|
<string name="import_backup">Przywróć kopię zapasową</string>
|
||||||
<string name="import_backup_password_hint">Jeśli kopia zapasowa nie jest chroniona hasłem, po prostu naciśnij Importuj, w przeciwnym razie wprowadź prawidłowe hasło.</string>
|
<string name="import_backup_password_hint">Jeśli kopia zapasowa nie jest chroniona hasłem, po prostu naciśnij Importuj, w przeciwnym razie wprowadź prawidłowe hasło.</string>
|
||||||
|
|
|
@ -1,77 +1,334 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="about">О приложении</string>
|
<string name="about">О приложении</string>
|
||||||
|
<string name="add_images">Добавить изображение</string>
|
||||||
<string name="add_item">Добавить пункт</string>
|
<string name="add_item">Добавить пункт</string>
|
||||||
<string name="add_label">Добавить метку</string>
|
<string name="add_label">Добавить метку</string>
|
||||||
|
<string name="add_reminder">Добавить напоминание</string>
|
||||||
|
<string name="adding_files">Добавление файлов</string>
|
||||||
|
<string name="adding_images">Добавление изображений</string>
|
||||||
|
<string name="all">Все</string>
|
||||||
<string name="appearance">Внешний вид</string>
|
<string name="appearance">Внешний вид</string>
|
||||||
<string name="archive">Архивировать</string>
|
<string name="archive">Архивировать</string>
|
||||||
<string name="archived">Архив</string>
|
<string name="archived">Архив</string>
|
||||||
|
<plurals name="archived_selected_notes">
|
||||||
|
<item quantity="few">Архивировано %1$d заметки</item>
|
||||||
|
<item quantity="one">Архивирована %1$d заметка</item>
|
||||||
|
<item quantity="other">Архивировано %1$d заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="ascending">По возрастанию</string>
|
||||||
|
<string name="attach_file">Прикрепить файл</string>
|
||||||
|
<string name="audio_recordings">Аудиозаписи</string>
|
||||||
|
<string name="auto_backup">Автобэкапы</string>
|
||||||
|
<string name="auto_backup_error_message">Ошибка при авто-резервной копии: \n\'%1$s\'\nПроверьте настройки или сообщите об ошибке</string>
|
||||||
|
<string name="auto_backup_failed">Сбой авто-резервирования NotallyX</string>
|
||||||
|
<string name="auto_backup_last">Последняя резервная копия</string>
|
||||||
|
<string name="auto_backup_on_save">Создавать резервную копию при выходе (авто)</string>
|
||||||
|
<string name="auto_backup_on_save_hint">При включении этой функции в папке резервных копий (Папка резервных копий) автоматически создаётся архив NotallyX_AutoBackup.zip при выходе из заметки.\nЭто может повлиять на производительность</string>
|
||||||
|
<string name="auto_backups_folder">Папка резервных копий</string>
|
||||||
|
<string name="auto_backups_folder_hint">Папка, в которой будут храниться резервные копии</string>
|
||||||
|
<string name="auto_backups_folder_rechoose">Необходимо повторно указать папку для резервных копий для доступа NotallyX.\nМожно отменить и пропустить импорт папки резервных копий</string>
|
||||||
|
<string name="auto_backups_folder_set">Сначала укажите папку для резервных копий</string>
|
||||||
|
<string name="auto_save_after_idle_time">Автосохранение заметки после указанного времени бездействия</string>
|
||||||
|
<string name="auto_sort_by_checked">Сортировать отмеченные элементы в конец</string>
|
||||||
|
<string name="back">Назад</string>
|
||||||
<string name="backup">Резервная копия</string>
|
<string name="backup">Резервная копия</string>
|
||||||
|
<string name="backup_password">Пароль для резервной копии</string>
|
||||||
|
<string name="backup_password_hint">При включении этой функции все новые ZIP-архивы резервных копий буду зашифрованы и защищены паролем</string>
|
||||||
|
<string name="backup_period_days">Интервал авторезервирования (в днях)</string>
|
||||||
|
<string name="backup_periodic">Переодические резервные копии</string>
|
||||||
|
<string name="backup_periodic_hint">При включении этой функции резервные копии будут автоматически сохраняться в указанную папку.\nФункция может не работать при включенном режиме энергосбережения</string>
|
||||||
|
<string name="behaviour">Биометрия</string>
|
||||||
|
<string name="biometric_lock">Блокировать приложение с помощью биометрии или PIN-кода</string>
|
||||||
|
<string name="biometrics_disable_success">Биометрическая защита/PIN-код отключены</string>
|
||||||
|
<string name="biometrics_failure">Ошибка аутентификации по Биометрии/PIN-коду</string>
|
||||||
|
<string name="biometrics_no_support">На устройстве нет биометрических функций</string>
|
||||||
|
<string name="biometrics_not_setup">Биометрия/PIN-код ещё не настроены</string>
|
||||||
|
<string name="biometrics_setup_success">Защита по Биометрии/PIN-коду включена</string>
|
||||||
<string name="bold">Жирный</string>
|
<string name="bold">Жирный</string>
|
||||||
|
<string name="calculating">Вычисление…</string>
|
||||||
<string name="cancel">Отменить</string>
|
<string name="cancel">Отменить</string>
|
||||||
|
<plurals name="cant_add_files">
|
||||||
|
<item quantity="few">Невозможно добавить %1$d файла</item>
|
||||||
|
<item quantity="one">Невозможно добавить %1$d файл</item>
|
||||||
|
<item quantity="other">Невозможно добавить %1$d файлов</item>
|
||||||
|
</plurals>
|
||||||
|
<plurals name="cant_add_images">
|
||||||
|
<item quantity="few">Невозможно добавить %1$d изображения</item>
|
||||||
|
<item quantity="one">Невозможно добавить %1$d изображение</item>
|
||||||
|
<item quantity="other">Невозможно добавить %1$d изображений</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="cant_find_folder">Папка не найдена. Возможно, она была перемещена или удалена</string>
|
||||||
|
<string name="cant_find_note">Заметка не найдена. Возможно она была удалена</string>
|
||||||
|
<string name="cant_load_file">Файл не загружен. Возможно, он был перемещён или удалён</string>
|
||||||
|
<string name="cant_load_image">Изображение не загружено. Проверьте, не было ли оно удалено</string>
|
||||||
<string name="cant_open_link">Невозможно открыть ссылку</string>
|
<string name="cant_open_link">Невозможно открыть ссылку</string>
|
||||||
<string name="change_color">Изменить цвет</string>
|
<string name="change_color">Изменить цвет</string>
|
||||||
|
<string name="change_color_message">Выберите цвет или создайте новый.\nЦвет можно изменить долгим нажатием.</string>
|
||||||
|
<string name="change_note">Изменить заметку</string>
|
||||||
|
<string name="check_all_items">Выделить всё</string>
|
||||||
|
<string name="choose_another_folder">Выбрать другую папку</string>
|
||||||
|
<string name="choose_folder">Выбрать папку</string>
|
||||||
|
<string name="choose_other_app">Выберите приложение для импорта</string>
|
||||||
|
<string name="clear">Очистить</string>
|
||||||
|
<string name="clear_data">Очистить данные</string>
|
||||||
|
<string name="clear_data_message">Все Заметки, Изображения, Файлы и Аудиозаписи будут навсегда удалены</string>
|
||||||
<string name="clear_formatting">Стандартный стиль</string>
|
<string name="clear_formatting">Стандартный стиль</string>
|
||||||
|
<string name="cleared_data">Все данные удалены</string>
|
||||||
|
<string name="color">Цвет</string>
|
||||||
|
<string name="color_exists">Этот цвет уже существует!</string>
|
||||||
<string name="content_density">Плотность контента</string>
|
<string name="content_density">Плотность контента</string>
|
||||||
|
<string name="continue_">Продолжить</string>
|
||||||
|
<string name="convert_to_list_note">Конвертировать в Список</string>
|
||||||
|
<string name="convert_to_text_note">Конвертировать в Заметку</string>
|
||||||
|
<string name="copied_link">Ссылка скопирована в буфер обмена</string>
|
||||||
|
<string name="copy">Копировать</string>
|
||||||
|
<string name="crash_message">Произошла непредвиденная ошибка.\nПриносим извинения за причиненные неудобства.</string>
|
||||||
|
<string name="create_github_issue">Сообщить об ошибке на GitHub</string>
|
||||||
|
<string name="creation_date">Создано</string>
|
||||||
|
<string name="custom">Пользовтаельский</string>
|
||||||
|
<string name="daily">Дни</string>
|
||||||
<string name="dark">Тёмная</string>
|
<string name="dark">Тёмная</string>
|
||||||
|
<string name="data_in_public">Сохранять данные в общедоступной папке</string>
|
||||||
|
<string name="data_in_public_message">При включении этой функции внутренняя база данных приложения будет перенесена в его общедоступную папку (Android/media/com.philkes.notallyx).\nЭто позволит синхронизировать данные NotallyX между устройствами с помощью приложений для синхронизации файлов.</string>
|
||||||
<string name="date">Дата</string>
|
<string name="date">Дата</string>
|
||||||
<string name="date_format">Формат даты</string>
|
<string name="date_format">Формат даты</string>
|
||||||
|
<string name="date_format_apply_in_note_view">Применять и в просмотре заметки</string>
|
||||||
|
<string name="date_format_hint">Применяет выбранный формат даты в общем списке заметок</string>
|
||||||
|
<string name="days">Дней</string>
|
||||||
<string name="delete">Удалить</string>
|
<string name="delete">Удалить</string>
|
||||||
<string name="delete_all">Удалить всё</string>
|
<string name="delete_all">Удалить всё</string>
|
||||||
<string name="delete_all_notes">Удалить все заметки\?</string>
|
<string name="delete_all_notes">Удалить все заметки\?</string>
|
||||||
|
<string name="delete_audio_recording_forever">Удалить все аудиозаписи\?</string>
|
||||||
|
<string name="delete_checked_items">Удалить выбранные элементы</string>
|
||||||
|
<string name="delete_color_message">Каким цветом заменить текущий цвет заметок\?</string>
|
||||||
|
<string name="delete_file">Удалить файл \'%1$s\'\?</string>
|
||||||
<string name="delete_forever">Удалить навсегда</string>
|
<string name="delete_forever">Удалить навсегда</string>
|
||||||
|
<string name="delete_image_forever">Удалить изображение навсегда\?</string>
|
||||||
<string name="delete_label">Удалить метку\?</string>
|
<string name="delete_label">Удалить метку\?</string>
|
||||||
<string name="delete_note_forever">Удалить заметку навсегда\?</string>
|
<string name="delete_note_forever">Удалить заметку навсегда\?</string>
|
||||||
|
<string name="delete_reminder">Удалить напоминание\?</string>
|
||||||
|
<string name="delete_selected_notes">Удалить выбранные заметки\?</string>
|
||||||
<string name="deleted">Удалённые</string>
|
<string name="deleted">Удалённые</string>
|
||||||
|
<plurals name="deleted_selected_notes">
|
||||||
|
<item quantity="few">Удалено %1$d заметки</item>
|
||||||
|
<item quantity="one">Удалена %1$d заметка</item>
|
||||||
|
<item quantity="other">Удалено %1$d заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="deleting_files">Удаление файлов</string>
|
||||||
|
<string name="deleting_images">Удаление изображений</string>
|
||||||
|
<string name="descending">По убыванию</string>
|
||||||
|
<string name="disable">Отключить</string>
|
||||||
|
<string name="disable_data_in_public">Переместить данные во внутреннюю папку</string>
|
||||||
|
<string name="disable_lock_description">Это также расшифрует базу данных</string>
|
||||||
|
<string name="disable_lock_title">Отключить защиту по Биометрии/PIN-коду</string>
|
||||||
|
<string name="disabled">Отключено</string>
|
||||||
|
<string name="disallow_screenshots">Запретить создание скриншотов</string>
|
||||||
|
<string name="discard">Отменить</string>
|
||||||
|
<string name="display_text">Текст для отображения</string>
|
||||||
|
<string name="donate">Сделать пожертвование</string>
|
||||||
<string name="drag_handle">Перетащить</string>
|
<string name="drag_handle">Перетащить</string>
|
||||||
<string name="edit">Редактировать</string>
|
<string name="edit">Редактировать</string>
|
||||||
|
<string name="edit_color">Изменить цвет</string>
|
||||||
<string name="edit_label">Изменить метку</string>
|
<string name="edit_label">Изменить метку</string>
|
||||||
<string name="empty_labels">Еще нет меток, создать\?</string>
|
<string name="edit_link">Изменить ссылку</string>
|
||||||
|
<string name="edit_reminders">Изменить напоминания</string>
|
||||||
|
<string name="elapsed">Просроченные</string>
|
||||||
|
<string name="empty_labels">Еще нет меток. Создать\?</string>
|
||||||
<string name="empty_list">Пустой список</string>
|
<string name="empty_list">Пустой список</string>
|
||||||
<string name="empty_note">Пустая заметка</string>
|
<string name="empty_note">Пустая заметка</string>
|
||||||
<string name="export">Экспортировать</string>
|
<string name="empty_reminders">Напоминаний нет. Создать\?</string>
|
||||||
<string name="export_backup">Экспортировать</string>
|
<string name="enable_lock_description">Это также зашифрует базу данных</string>
|
||||||
|
<string name="enable_lock_title">Включить защиту по Биометрии/PIN-коду</string>
|
||||||
|
<string name="enabled">Включено</string>
|
||||||
|
<string name="error_while_renaming_file">Ошибка переименования файла</string>
|
||||||
|
<string name="error_while_renaming_image">Ошибка переименования изображения</string>
|
||||||
|
<string name="evernote">Evernote</string>
|
||||||
|
<string name="evernote_help">Чтобы импортировать заметки из Evernote, экспортируйте блокнот в формате ENEX. Нажмите \"Помощь\" для инструкций.\n\nЕсли файл ENEX уже готов, выберите \"Импорт\"</string>
|
||||||
|
<string name="every">Каждый</string>
|
||||||
|
<string name="export">Экспорт</string>
|
||||||
|
<string name="export_backup">Экспорт резервной копии</string>
|
||||||
|
<string name="export_settings">Экспорт настроек</string>
|
||||||
|
<string name="export_settings_failure">Не удалось экспортировать настройки. Возможно, указан недопустимый путь</string>
|
||||||
|
<string name="export_settings_message">Все настройки будут сохранены в JSON-файл для последующего импорта.\n\nОбратите внимание: зашифрованные данные (пароль авторезерва и биометрический ключ) не экспортируются.</string>
|
||||||
|
<string name="export_settings_success">Настройки успешно экспортированы</string>
|
||||||
|
<string name="exporting_backup">Экспорт резервной копии</string>
|
||||||
|
<string name="extracted_files">Файлы извлечены</string>
|
||||||
<string name="filter">Фильтры</string>
|
<string name="filter">Фильтры</string>
|
||||||
|
<string name="folder">Папка</string>
|
||||||
<string name="follow_system">Как в системе</string>
|
<string name="follow_system">Как в системе</string>
|
||||||
|
<string name="google_keep">Google Keep</string>
|
||||||
|
<string name="google_keep_help">Для импорта из Google Keep скачайте архив Takeout (только данные \"Keep\"). Нажмите \"Помощь\" для подробностей.\n\nЕсли архив уже есть, выберите его через \"Импорт\"</string>
|
||||||
<string name="grid">Сетка</string>
|
<string name="grid">Сетка</string>
|
||||||
<string name="import_backup">Импортировать</string>
|
<string name="help">Помощь</string>
|
||||||
|
<string name="hours">Часов</string>
|
||||||
|
<string name="image_format_not_supported">Формат изображения не поддерживыется</string>
|
||||||
|
<string name="images_hidden_in_overview">Если эта функция включена, изображения заметок будут скрыты из общего списка.</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Скрыть изображения в общем списке</string>
|
||||||
|
<string name="import_action">Импорт</string>
|
||||||
|
<string name="import_backup">Импорт резервной копии</string>
|
||||||
|
<string name="import_backup_password_hint">Если резервная копия не защищена паролем, нажмите \"Импорт\". В противном случае введите пароль.</string>
|
||||||
|
<string name="import_other">Импорт заметок из других приложений</string>
|
||||||
|
<string name="import_settings">Импорт настроек</string>
|
||||||
|
<string name="import_settings_failure">Не удалось импортировать настройки. Вы выбрали правильный файл\?</string>
|
||||||
|
<string name="import_settings_message">Для импорта настроек выберите корректный JSON-файл настроек NotallyX. </string>
|
||||||
|
<string name="import_settings_success">Настройки успешно импортированы</string>
|
||||||
|
<string name="imported_files">Файлы импортированы</string>
|
||||||
|
<string name="imported_notes">Заметки импортированы</string>
|
||||||
|
<plurals name="imported_notes">
|
||||||
|
<item quantity="few">Импортировано %1$s заметки</item>
|
||||||
|
<item quantity="one">Импортирована %1$s заметка</item>
|
||||||
|
<item quantity="other">Импортировано %1$s заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="importing_backup">Импорт резервной копии</string>
|
||||||
|
<string name="insert_an_sd_card_audio">Вставьте SD-карту для записи аудио</string>
|
||||||
|
<string name="insert_an_sd_card_files">Для добавления файлов вставьте SD-карту</string>
|
||||||
|
<string name="insert_an_sd_card_images">Вставьте SD-карту, чтобы загрузить изображение</string>
|
||||||
|
<string name="install_a_browser">Чтобы открыть ссылку, установите браузер</string>
|
||||||
|
<string name="install_an_email">Установите клиент почты для отправки отзыва</string>
|
||||||
|
<string name="invalid_backup">Некорректная резервная копия</string>
|
||||||
|
<string name="invalid_evernote">Недопустимый файл Evernote (ENEX)</string>
|
||||||
|
<string name="invalid_google_keep">Некорректный ZIP-архив Google Takeout</string>
|
||||||
|
<string name="invalid_image">Изображение повреждено</string>
|
||||||
|
<string name="invalid_link">Скопируйте рабочую ссылку в буфер обмена</string>
|
||||||
<string name="italic">Курсив</string>
|
<string name="italic">Курсив</string>
|
||||||
<string name="item">Пункт</string>
|
<string name="item">Пункт</string>
|
||||||
|
<string name="json_files">JSON файлы</string>
|
||||||
|
<string name="json_files_help">Для импорта заметок из JSON-файлов (отдельный файл или папка) нажмите \"Импорт\". Каждый корректный JSON-файл будет преобразован в отдельную заметку, где имя файла станет её заголовком</string>
|
||||||
<string name="label_exists">Метка уже существует</string>
|
<string name="label_exists">Метка уже существует</string>
|
||||||
|
<string name="label_visibility">Скрыть/показать метку в панели навигации</string>
|
||||||
<string name="labels">Метки</string>
|
<string name="labels">Метки</string>
|
||||||
|
<string name="labels_hidden_in_overview">При включении этой функции метки заметок будут скрыты в общем списке</string>
|
||||||
|
<string name="labels_hidden_in_overview_title">Скрывать метки в общем списке</string>
|
||||||
|
<string name="large">Большой</string>
|
||||||
<string name="libraries">Библиотеки</string>
|
<string name="libraries">Библиотеки</string>
|
||||||
<string name="light">Светлая</string>
|
<string name="light">Светлая</string>
|
||||||
<string name="link">Ссылка</string>
|
<string name="link">Ссылка</string>
|
||||||
|
<string name="link_note">Связать заметку</string>
|
||||||
<string name="list">Список</string>
|
<string name="list">Список</string>
|
||||||
|
<string name="list_item_auto_sort">Сортировать элементы списка</string>
|
||||||
|
<string name="locked">Заблокировано</string>
|
||||||
|
<string name="make_feature_request">Запросить новую функцию</string>
|
||||||
<string name="make_list">Создать список</string>
|
<string name="make_list">Создать список</string>
|
||||||
|
<string name="max_backups">Лимит резервных копий</string>
|
||||||
<string name="max_items_to_display">Максимум отображаемых пунктов в списке</string>
|
<string name="max_items_to_display">Максимум отображаемых пунктов в списке</string>
|
||||||
|
<string name="max_labels_to_display">Максимум отображаемых меток</string>
|
||||||
<string name="max_lines_to_display">Максимум отображаемых строк в заметке</string>
|
<string name="max_lines_to_display">Максимум отображаемых строк в заметке</string>
|
||||||
|
<string name="max_lines_to_display_title">Максимум отображаемых строк в заголовке</string>
|
||||||
|
<string name="medium">Средний</string>
|
||||||
|
<string name="minutes">Минуты</string>
|
||||||
|
<string name="modified_date">Изменено</string>
|
||||||
<string name="monospace">Моноширинный</string>
|
<string name="monospace">Моноширинный</string>
|
||||||
|
<string name="monthly">Ежемесячно</string>
|
||||||
|
<string name="months">Месяцы</string>
|
||||||
|
<string name="more">Ещё %1$d</string>
|
||||||
|
<plurals name="more_files">
|
||||||
|
<item quantity="few">...ещё %1$d файла</item>
|
||||||
|
<item quantity="one">...ещё %1$d файл</item>
|
||||||
|
<item quantity="other">...ещё %1$d файлов</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="new_color">Новый цвет</string>
|
||||||
|
<string name="next">Следующий</string>
|
||||||
|
<string name="no_auto_sort">Без автосортировки</string>
|
||||||
<string name="none">Отсутствует</string>
|
<string name="none">Отсутствует</string>
|
||||||
<string name="note">Заметка</string>
|
<string name="note">Заметка</string>
|
||||||
<string name="notes">Заметки</string>
|
<string name="notes">Заметки</string>
|
||||||
|
<string name="notes_sorted_by">Сортировать заметки по</string>
|
||||||
<string name="open_link">Открыть ссылку</string>
|
<string name="open_link">Открыть ссылку</string>
|
||||||
|
<string name="open_note">Открыть заметку</string>
|
||||||
<string name="others">Другие</string>
|
<string name="others">Другие</string>
|
||||||
|
<string name="pause">Пауза</string>
|
||||||
|
<string name="paused">Приостановлено</string>
|
||||||
<string name="pin">Закрепить</string>
|
<string name="pin">Закрепить</string>
|
||||||
<string name="pinned">Закреплённые</string>
|
<string name="pinned">Закреплённые</string>
|
||||||
|
<string name="plain_text_files">Текстовые файлы</string>
|
||||||
|
<string name="plain_text_files_help">Для импорта заметок из текстовых файлов (отдельный файл или папка) нажмите \"Импорт\". Каждый файл станет отдельной заметкой — имя файла будет её заголовком. Если текст начинается с элементов списка (например, Markdown \"- [x]\", синтаксис NotallyX \"[~/\", или \"*\", ~), он преобразуется в заметку-список.</string>
|
||||||
|
<string name="play">Играть</string>
|
||||||
|
<string name="please_grant_notally_alarm">Разрешите NotallyX отправлять напоминания</string>
|
||||||
|
<string name="please_grant_notally_audio">Разрешите доступ к микрофону.\nЗаписи остаются на вашем устройстве</string>
|
||||||
|
<string name="please_grant_notally_notification">Разрешите отправку уведомлений</string>
|
||||||
|
<string name="please_grant_notally_notification_auto_backup">При сбое авторезервирования вы получите уведомление, если разрешите их отправку. </string>
|
||||||
|
<string name="previous">Предыдущий</string>
|
||||||
<string name="rate">Оценить приложение</string>
|
<string name="rate">Оценить приложение</string>
|
||||||
|
<string name="read_only">Только чтение</string>
|
||||||
|
<string name="ready_to_record">Готово к записи</string>
|
||||||
|
<string name="record_audio">Запись аудио</string>
|
||||||
|
<string name="recording">Запись…</string>
|
||||||
|
<string name="redo">Вернуть</string>
|
||||||
|
<string name="reminder_no_repetition">Без повтора</string>
|
||||||
|
<string name="reminders">Напоминания</string>
|
||||||
|
<string name="remove_link">Удалить ссылку</string>
|
||||||
|
<string name="repetition">Повтор</string>
|
||||||
|
<string name="repetition_custom">Свой вариант повтора</string>
|
||||||
|
<string name="repetition_value_hint">Значение</string>
|
||||||
|
<string name="report_bug">Сообщить об ошибке/баге</string>
|
||||||
|
<string name="report_crash">Отправить отчёт об ошибке</string>
|
||||||
|
<string name="reset_settings">Сбросить настройки</string>
|
||||||
|
<string name="reset_settings_message">Все настройки будут сброшены к значениям по умолчанию</string>
|
||||||
|
<string name="reset_settings_success">Все настройки успешно сброшены</string>
|
||||||
|
<string name="restart_app">Перезапустить приложение</string>
|
||||||
<string name="restore">Восстановить</string>
|
<string name="restore">Восстановить</string>
|
||||||
|
<plurals name="restored_selected_notes">
|
||||||
|
<item quantity="few">Восстановлено %1$d заметки</item>
|
||||||
|
<item quantity="one">Восстановлена %1$d заметка</item>
|
||||||
|
<item quantity="other">Восстановлено %1$d заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="resume">Повторить</string>
|
||||||
<string name="save">Сохранить</string>
|
<string name="save">Сохранить</string>
|
||||||
|
<string name="save_recording">Сохранить аудиозапись\?</string>
|
||||||
<string name="save_to_device">Сохранить на устройстве</string>
|
<string name="save_to_device">Сохранить на устройстве</string>
|
||||||
<string name="saved_to_device">Сохранено на устройстве</string>
|
<string name="saved_to_device">Сохранено на устройстве</string>
|
||||||
<string name="saved_to_notally">Сохранено в NotallyX</string>
|
<string name="saved_to_notally">Сохранено в NotallyX</string>
|
||||||
<string name="search">Поиск</string>
|
<string name="search">Поиск</string>
|
||||||
|
<string name="security">Безопасность</string>
|
||||||
|
<string name="select_all">Выбрать всё</string>
|
||||||
|
<string name="select_labels">Выбрать метки</string>
|
||||||
|
<string name="select_note">Выбрать заметки</string>
|
||||||
|
<string name="send_feedback">Сообщить о проблеме</string>
|
||||||
<string name="settings">Настройки</string>
|
<string name="settings">Настройки</string>
|
||||||
<string name="share">Поделиться</string>
|
<string name="share">Поделиться</string>
|
||||||
|
<string name="skip">Пропустить</string>
|
||||||
|
<string name="small">Маленький</string>
|
||||||
<string name="something_went_wrong">Что-то пошло не так. Пожалуйста, повторите попытку</string>
|
<string name="something_went_wrong">Что-то пошло не так. Пожалуйста, повторите попытку</string>
|
||||||
|
<string name="sort_direction">Порядок сортировки</string>
|
||||||
|
<string name="source_code">Исходный код</string>
|
||||||
|
<string name="start">Старт</string>
|
||||||
|
<string name="start_view">Стартовый экран</string>
|
||||||
|
<string name="start_view_hint">Выберите, какой экран/ярлык показывать при запуске.\nПо умолчанию — основной список заметок</string>
|
||||||
|
<string name="stop">Стоп</string>
|
||||||
<string name="strikethrough">Перечёркнутый</string>
|
<string name="strikethrough">Перечёркнутый</string>
|
||||||
<string name="take_note">Создать заметку</string>
|
<string name="take_note">Создать заметку</string>
|
||||||
|
<string name="tap_for_more_options">Нажмите для подробностей</string>
|
||||||
|
<string name="tap_to_set_up">Нажмите для настройки</string>
|
||||||
|
<string name="text_default">По умолчанию</string>
|
||||||
|
<string name="text_size">Размер текста</string>
|
||||||
<string name="theme">Тема</string>
|
<string name="theme">Тема</string>
|
||||||
|
<string name="theme_use_dynamic_colors">Использовать цвета обоев</string>
|
||||||
<string name="title">Заголовок</string>
|
<string name="title">Заголовок</string>
|
||||||
|
<string name="to_record_audio">Что-бы записывать аудиозаписи, разрешите NotallyX доступ к микрофону</string>
|
||||||
<string name="unarchive">Разархивировать</string>
|
<string name="unarchive">Разархивировать</string>
|
||||||
|
<plurals name="unarchived_selected_notes">
|
||||||
|
<item quantity="few">Извлечено %1$d заметки</item>
|
||||||
|
<item quantity="one">Извлечено %1$d заметка</item>
|
||||||
|
<item quantity="other">Извлечено %1$d заметок</item>
|
||||||
|
</plurals>
|
||||||
|
<string name="uncheck_all_items">Очистить выбор</string>
|
||||||
|
<string name="undo">Вернуть</string>
|
||||||
|
<string name="unknown_error">Неизвестная ошибка</string>
|
||||||
|
<string name="unknown_name">Неизвестное имя</string>
|
||||||
|
<string name="unlabeled">Без названия</string>
|
||||||
|
<string name="unlock">Разблокировка по Биометрии/PIN-коду</string>
|
||||||
|
<string name="unlock_with_biometrics_not_setup">Ранее вы включили биометрическую защиту, но сейчас на устройстве не настроены биометрия или PIN-код.\n\nНажмите «Отключить», чтобы снять блокировку, или настройте биометрию/PIN-код заново</string>
|
||||||
<string name="unpin">Открепить</string>
|
<string name="unpin">Открепить</string>
|
||||||
|
<string name="upcoming">Предстоящие</string>
|
||||||
|
<string name="updated_link">Обновить ссылку</string>
|
||||||
<string name="view">Вид</string>
|
<string name="view">Вид</string>
|
||||||
<string name="view_file">Посмотреть файл</string>
|
<string name="view_file">Посмотреть файл</string>
|
||||||
<string name="view_note">Посмотреть заметку…</string>
|
<string name="view_note">Посмотреть заметку…</string>
|
||||||
|
<string name="weekly">Еженедельный</string>
|
||||||
|
<string name="weeks">Недели</string>
|
||||||
|
<string name="wrong_password">Неверный пароль</string>
|
||||||
|
<string name="yearly">Ежегодный</string>
|
||||||
|
<string name="years">Года</string>
|
||||||
<string name="your_notes_associated">Ваши заметки, связанные с этой меткой, не будут удалены</string>
|
<string name="your_notes_associated">Ваши заметки, связанные с этой меткой, не будут удалены</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -118,6 +118,7 @@
|
||||||
<string name="disable_lock_description">这也会解密数据据</string>
|
<string name="disable_lock_description">这也会解密数据据</string>
|
||||||
<string name="disable_lock_title">停用生物特征/PIN锁</string>
|
<string name="disable_lock_title">停用生物特征/PIN锁</string>
|
||||||
<string name="disabled">禁用</string>
|
<string name="disabled">禁用</string>
|
||||||
|
<string name="disallow_screenshots">禁止截屏</string>
|
||||||
<string name="discard">取消</string>
|
<string name="discard">取消</string>
|
||||||
<string name="display_text">要展示的我呢本</string>
|
<string name="display_text">要展示的我呢本</string>
|
||||||
<string name="donate">捐赠</string>
|
<string name="donate">捐赠</string>
|
||||||
|
@ -157,6 +158,8 @@
|
||||||
<string name="help">帮助</string>
|
<string name="help">帮助</string>
|
||||||
<string name="hours">小时</string>
|
<string name="hours">小时</string>
|
||||||
<string name="image_format_not_supported">不支持该图片格式</string>
|
<string name="image_format_not_supported">不支持该图片格式</string>
|
||||||
|
<string name="images_hidden_in_overview">如果启用此选项,则注释的图像将不会显示在概览中。</string>
|
||||||
|
<string name="images_hidden_in_overview_title">在概览中隐藏图片</string>
|
||||||
<string name="import_action">导入</string>
|
<string name="import_action">导入</string>
|
||||||
<string name="import_backup">导入备份</string>
|
<string name="import_backup">导入备份</string>
|
||||||
<string name="import_backup_password_hint">如果你的备份文件没有密码保护,只需按下“导入”即可。如有,起输入正确的密码</string>
|
<string name="import_backup_password_hint">如果你的备份文件没有密码保护,只需按下“导入”即可。如有,起输入正确的密码</string>
|
||||||
|
|
|
@ -134,6 +134,8 @@
|
||||||
<string name="help">幫助</string>
|
<string name="help">幫助</string>
|
||||||
<string name="hours">小時</string>
|
<string name="hours">小時</string>
|
||||||
<string name="image_format_not_supported">不支持的圖片格式</string>
|
<string name="image_format_not_supported">不支持的圖片格式</string>
|
||||||
|
<string name="images_hidden_in_overview">啟用此功能後,筆記的圖像將隱藏在概覽中。</string>
|
||||||
|
<string name="images_hidden_in_overview_title">在概覽中隱藏圖片</string>
|
||||||
<string name="import_action">匯入</string>
|
<string name="import_action">匯入</string>
|
||||||
<string name="import_backup">匯入備份</string>
|
<string name="import_backup">匯入備份</string>
|
||||||
<string name="import_backup_password_hint">如果您的備份沒有密碼保護,只需按匯入,否則請輸入正確的密碼。</string>
|
<string name="import_backup_password_hint">如果您的備份沒有密碼保護,只需按匯入,否則請輸入正確的密碼。</string>
|
||||||
|
|
|
@ -161,6 +161,8 @@
|
||||||
<string name="help">Help</string>
|
<string name="help">Help</string>
|
||||||
<string name="hours">Hours</string>
|
<string name="hours">Hours</string>
|
||||||
<string name="image_format_not_supported">Image format not supported</string>
|
<string name="image_format_not_supported">Image format not supported</string>
|
||||||
|
<string name="images_hidden_in_overview">By enabling this, the notes’ images will be hidden in the overview</string>
|
||||||
|
<string name="images_hidden_in_overview_title">Hide Images in Overview</string>
|
||||||
<string name="import_action">Import</string>
|
<string name="import_action">Import</string>
|
||||||
<string name="import_backup">Import backup</string>
|
<string name="import_backup">Import backup</string>
|
||||||
<string name="import_backup_password_hint">If your backup is not password-protected simply press Import, otherwise enter the correct password.</string>
|
<string name="import_backup_password_hint">If your backup is not password-protected simply press Import, otherwise enter the correct password.</string>
|
||||||
|
|
Binary file not shown.
|
@ -20,6 +20,6 @@ org.gradle.jvmargs=-Xmx1536m -XX:+UseParallelGC
|
||||||
org.gradle.parallel=true
|
org.gradle.parallel=true
|
||||||
android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
android.experimental.enableNewResourceShrinker.preciseShrinking=true
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
app.lastVersionName=7.3.1
|
app.lastVersionName=7.4.0
|
||||||
app.versionCode=7400
|
app.versionCode=7410
|
||||||
app.versionName=7.4.0
|
app.versionName=7.4.1
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue