├── .github
└── FUNDING.yml
├── .gitignore
├── Audio-Note-Demo-Google-Play.mp4
├── CONTRIBUTING.md
├── CONTRIBUTORS.md
├── LICENSE.md
├── Privacy-Policy.txt
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
├── schemas
│ └── com.omgodse.notally.room.NotallyDatabase
│ │ ├── 1.json
│ │ ├── 2.json
│ │ ├── 3.json
│ │ ├── 4.json
│ │ └── 5.json
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ ├── android
│ │ └── print
│ │ │ └── PostPDFGenerator.kt
│ └── com
│ │ └── omgodse
│ │ └── notally
│ │ ├── ActionMode.kt
│ │ ├── AttachmentDeleteService.kt
│ │ ├── AutoBackupWorker.kt
│ │ ├── Cache.kt
│ │ ├── LinkMovementMethod.kt
│ │ ├── MenuDialog.kt
│ │ ├── NotallyApplication.kt
│ │ ├── OverflowEditText.kt
│ │ ├── Progress.kt
│ │ ├── ReminderReceiver.kt
│ │ ├── activities
│ │ ├── ConfigureWidget.kt
│ │ ├── MainActivity.kt
│ │ ├── MakeList.kt
│ │ ├── NotallyActivity.kt
│ │ ├── PlayAudio.kt
│ │ ├── RecordAudio.kt
│ │ ├── SelectLabels.kt
│ │ ├── TakeNote.kt
│ │ └── ViewImage.kt
│ │ ├── audio
│ │ ├── AudioControlView.kt
│ │ ├── AudioPlayService.kt
│ │ ├── AudioRecordService.kt
│ │ ├── LocalBinder.kt
│ │ └── Status.kt
│ │ ├── fragments
│ │ ├── Archived.kt
│ │ ├── Deleted.kt
│ │ ├── DisplayLabel.kt
│ │ ├── Labels.kt
│ │ ├── NotallyFragment.kt
│ │ ├── Notes.kt
│ │ ├── Search.kt
│ │ └── Settings.kt
│ │ ├── image
│ │ ├── AspectRatioRecyclerView.kt
│ │ ├── Event.kt
│ │ └── ImageError.kt
│ │ ├── legacy
│ │ ├── Migrations.kt
│ │ └── XMLUtils.kt
│ │ ├── miscellaneous
│ │ ├── Constants.kt
│ │ ├── Export.kt
│ │ ├── Extensions.kt
│ │ ├── IO.kt
│ │ └── Operations.kt
│ │ ├── preferences
│ │ ├── BetterLiveData.kt
│ │ ├── ListInfo.kt
│ │ ├── Preferences.kt
│ │ ├── SeekbarInfo.kt
│ │ └── TextInfo.kt
│ │ ├── recyclerview
│ │ ├── DragCallback.kt
│ │ ├── ItemListener.kt
│ │ ├── ListItemListener.kt
│ │ ├── StringDiffCallback.kt
│ │ ├── adapter
│ │ │ ├── AudioAdapter.kt
│ │ │ ├── BaseNoteAdapter.kt
│ │ │ ├── ColorAdapter.kt
│ │ │ ├── ErrorAdapter.kt
│ │ │ ├── ImageAdapter.kt
│ │ │ ├── LabelAdapter.kt
│ │ │ ├── MakeListAdapter.kt
│ │ │ ├── PreviewImageAdapter.kt
│ │ │ └── SelectableLabelAdapter.kt
│ │ └── viewholder
│ │ │ ├── AudioVH.kt
│ │ │ ├── BaseNoteVH.kt
│ │ │ ├── ColorVH.kt
│ │ │ ├── ErrorVH.kt
│ │ │ ├── HeaderVH.kt
│ │ │ ├── ImageVH.kt
│ │ │ ├── LabelVH.kt
│ │ │ ├── MakeListVH.kt
│ │ │ ├── PreviewImageVH.kt
│ │ │ └── SelectableLabelVH.kt
│ │ ├── room
│ │ ├── Attachment.kt
│ │ ├── Audio.kt
│ │ ├── BaseNote.kt
│ │ ├── Color.kt
│ │ ├── Converters.kt
│ │ ├── Folder.kt
│ │ ├── Frequency.kt
│ │ ├── Header.kt
│ │ ├── IdLabels.kt
│ │ ├── IdReminder.kt
│ │ ├── Image.kt
│ │ ├── Item.kt
│ │ ├── Label.kt
│ │ ├── ListItem.kt
│ │ ├── NotallyDatabase.kt
│ │ ├── Reminder.kt
│ │ ├── SpanRepresentation.kt
│ │ ├── Type.kt
│ │ ├── dao
│ │ │ ├── BaseNoteDao.kt
│ │ │ ├── CommonDao.kt
│ │ │ └── LabelDao.kt
│ │ └── livedata
│ │ │ ├── Content.kt
│ │ │ └── SearchResult.kt
│ │ ├── viewmodels
│ │ ├── BaseNoteModel.kt
│ │ ├── Extensions.kt
│ │ ├── LabelModel.kt
│ │ └── NotallyModel.kt
│ │ └── widget
│ │ ├── WidgetFactory.kt
│ │ ├── WidgetProvider.kt
│ │ └── WidgetService.kt
│ └── res
│ ├── color
│ ├── chip_background.xml
│ ├── chip_stroke.xml
│ ├── navigation_view_item.xml
│ ├── stop_background.xml
│ └── stop_text.xml
│ ├── drawable-v26
│ ├── make_list.xml
│ └── take_note.xml
│ ├── drawable
│ ├── add.xml
│ ├── add_16.xml
│ ├── add_images.xml
│ ├── archive.xml
│ ├── change_color.xml
│ ├── checkbox.xml
│ ├── checkbox_16.xml
│ ├── checkbox_fill.xml
│ ├── checkbox_outline.xml
│ ├── checkbox_outline_16.xml
│ ├── checked_circle.xml
│ ├── copy.xml
│ ├── delete.xml
│ ├── delete_all.xml
│ ├── drag_handle.xml
│ ├── edit.xml
│ ├── export.xml
│ ├── home.xml
│ ├── label.xml
│ ├── make_list.xml
│ ├── make_list_foreground.xml
│ ├── notally_foreground.xml
│ ├── notebook.xml
│ ├── notification_delete.xml
│ ├── notification_reminder.xml
│ ├── pin.xml
│ ├── record_audio.xml
│ ├── reminder.xml
│ ├── reminder_16.xml
│ ├── reminder_20.xml
│ ├── restore.xml
│ ├── rounded_rectangle.xml
│ ├── save.xml
│ ├── search.xml
│ ├── settings.xml
│ ├── share.xml
│ ├── take_note.xml
│ ├── take_note_foreground.xml
│ ├── unarchive.xml
│ └── unpin.xml
│ ├── layout-v31
│ └── widget_list_item.xml
│ ├── layout
│ ├── activity_configure_widget.xml
│ ├── activity_label.xml
│ ├── activity_main.xml
│ ├── activity_notally.xml
│ ├── activity_play_audio.xml
│ ├── activity_record_audio.xml
│ ├── activity_view_image.xml
│ ├── dialog_color.xml
│ ├── dialog_input.xml
│ ├── dialog_progress.xml
│ ├── dialog_reminder.xml
│ ├── drawer_header.xml
│ ├── error.xml
│ ├── fragment_notes.xml
│ ├── fragment_settings.xml
│ ├── label.xml
│ ├── menu_item.xml
│ ├── preference.xml
│ ├── preference_seekbar.xml
│ ├── recycler_audio.xml
│ ├── recycler_base_note.xml
│ ├── recycler_color.xml
│ ├── recycler_header.xml
│ ├── recycler_image.xml
│ ├── recycler_label.xml
│ ├── recycler_list_item.xml
│ ├── recycler_preview_image.xml
│ ├── recycler_selectable_label.xml
│ ├── widget.xml
│ ├── widget_list_header.xml
│ ├── widget_list_item.xml
│ ├── widget_note.xml
│ └── widget_preview.xml
│ ├── mipmap-anydpi-v26
│ ├── notally.xml
│ └── notally_round.xml
│ ├── mipmap-hdpi
│ ├── notally.png
│ └── notally_round.png
│ ├── mipmap-mdpi
│ ├── notally.png
│ └── notally_round.png
│ ├── mipmap-nodpi
│ └── widget_preview.png
│ ├── mipmap-xhdpi
│ ├── notally.png
│ └── notally_round.png
│ ├── mipmap-xxhdpi
│ ├── notally.png
│ └── notally_round.png
│ ├── mipmap-xxxhdpi
│ ├── notally.png
│ └── notally_round.png
│ ├── navigation
│ └── navigation.xml
│ ├── raw
│ └── keep.xml
│ ├── resources.properties
│ ├── values-ca
│ └── strings.xml
│ ├── values-cs
│ └── strings.xml
│ ├── values-da
│ └── strings.xml
│ ├── values-de
│ └── strings.xml
│ ├── values-el
│ └── strings.xml
│ ├── values-es
│ └── strings.xml
│ ├── values-fr
│ └── strings.xml
│ ├── values-hu
│ └── strings.xml
│ ├── values-in
│ └── strings.xml
│ ├── values-it
│ └── strings.xml
│ ├── values-ja
│ └── strings.xml
│ ├── values-ko
│ └── strings.xml
│ ├── values-my
│ └── strings.xml
│ ├── values-nb
│ └── strings.xml
│ ├── values-night
│ ├── colors.xml
│ └── styles.xml
│ ├── values-nl
│ └── strings.xml
│ ├── values-nn
│ └── strings.xml
│ ├── values-pl
│ └── strings.xml
│ ├── values-pt-rBR
│ └── strings.xml
│ ├── values-pt-rPT
│ └── strings.xml
│ ├── values-ro
│ └── strings.xml
│ ├── values-ru
│ └── strings.xml
│ ├── values-sk
│ └── strings.xml
│ ├── values-sl
│ └── strings.xml
│ ├── values-sv
│ └── strings.xml
│ ├── values-tl
│ └── strings.xml
│ ├── values-tr
│ └── strings.xml
│ ├── values-uk
│ └── strings.xml
│ ├── values-v23
│ └── styles.xml
│ ├── values-vi
│ └── strings.xml
│ ├── values-zh-rCN
│ └── strings.xml
│ ├── values
│ ├── arrays.xml
│ ├── attrs.xml
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ ├── backup_content.xml
│ ├── data_rules.xml
│ ├── provider_paths.xml
│ ├── shortcuts.xml
│ └── widget.xml
├── build.gradle
├── fastlane
└── metadata
│ └── android
│ ├── en-US
│ ├── changelogs
│ │ ├── 40.txt
│ │ ├── 41.txt
│ │ ├── 43.txt
│ │ ├── 44.txt
│ │ ├── 45.txt
│ │ ├── 46.txt
│ │ ├── 47.txt
│ │ ├── 50.txt
│ │ ├── 51.txt
│ │ ├── 52.txt
│ │ ├── 53.txt
│ │ ├── 54.txt
│ │ ├── 55.txt
│ │ └── 56.txt
│ ├── full_description.txt
│ ├── images
│ │ ├── icon.png
│ │ └── phoneScreenshots
│ │ │ ├── 1.png
│ │ │ ├── 2.png
│ │ │ ├── 3.png
│ │ │ ├── 4.png
│ │ │ ├── 5.png
│ │ │ ├── 6.png
│ │ │ ├── 7.png
│ │ │ └── 8.png
│ ├── short_description.txt
│ └── title.txt
│ └── fr-FR
│ ├── full_description.txt
│ ├── short_description.txt
│ └── title.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | patreon: omgodse
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 |
--------------------------------------------------------------------------------
/Audio-Note-Demo-Google-Play.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/OmGodse/Notally/f6569646d84b7be9d188735caf8a99cf61fa5275/Audio-Note-Demo-Google-Play.mp4
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ### Contributing
2 |
3 | Issues are currently disabled.
4 |
5 | Please use the pull requests tab only for translations or bug fixes.
--------------------------------------------------------------------------------
/CONTRIBUTORS.md:
--------------------------------------------------------------------------------
1 | ### Translations
2 | 1. 🇬🇧 English
3 | 2. 🇭🇺 Hungarian
4 | 3. 🇬🇷 Greek by xamps
5 | 4. 🇳🇱 Dutch by [tlmnot](https://github.com/tlmnot)
6 | 5. 🇨🇿 Czech by [tomo90](https://github.com/tomo90)
7 | 6. 🇯🇵 Japanese by [kato-k](https://github.com/kato-k)
8 | 7. 🇦🇩 Catalan by retiolus
9 | 8. 🇵🇱 Polish by [ZiomaleQ](https://github.com/ZiomaleQ), [rehork](https://github.com/rehork)
10 | 9. 🇸🇰 Slovak by [Juraj Liso](https://github.com/LiJu09)
11 | 10. 🇮🇩 Indonesian by [zmni](https://github.com/zmni)
12 | 11. 🇮🇹 Italian by Luigi Sforza, [IlmastroStefanuzzo](https://github.com/IlmastroStefanuzzo)
13 | 12. 🇪🇸 Spanish by Jose Casas
14 | 13. 🇹🇷 Turkish by Helpful User
15 | 14. 🇺🇦 Ukrainian by Alex Shpak
16 | 15. 🇩🇰 Danish by [shoddysheep](https://github.com/shoddysheep)
17 | 16. 🇸🇪 Swedish by Erik Lindström
18 | 17. 🇷🇺 Russian by Denis Bondarenko, Lyyako
19 | 18. 🇫🇷 French by Arnaud Dieumegard, [Co-7](https://github.com/Co-7)
20 | 19. 🇧🇷 Portuguese (Brazil) by [fabianski7](https://github.com/fabianski7)
21 | 20. 🇵🇹 Portuguese (Portugal) by [joaopmatos](https://github.com/joaopmatos)
22 | 21. 🇳🇴 Norwegian (Bokmål) by Fredrik Magnussen, [Erik Thom](https://github.com/erikthm)
23 | 22. 🇳🇴 Norwegian (Nynorsk) by [Erik Thom](https://github.com/erikthm)
24 | 23. 🇵🇭 Tagalog by Isaiah Collins Abetong
25 | 24. 🇨🇳 Chinese (Simplified) by [Austin Huang](https://github.com/austinhuang0131), [sr093906](https://github.com/sr093906)
26 | 25. 🇩🇪 German by Maximilian Braunschmied, [jonas-haeusler](https://github.com/jonas-haeusler),
27 | [samuel141](https://github.com/samuel141), [nautilusx](https://github.com/nautilusx)
28 | 26. 🇻🇳 Vietnamese by [mastoduy](https://github.com/mastoduy)
--------------------------------------------------------------------------------
/Privacy-Policy.txt:
--------------------------------------------------------------------------------
1 | No user data is collected
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Background
2 | Notally was created because I wanted to make something that was beautiful and at the same time, useful. It's extremely light, there are minimal dependencies and lines of code.
3 |
4 | ### Features
5 | * Widgets
6 | * Auto backup
7 | * Adjustable text size
8 | * Support for Lollipop devices and up
9 | * APK size of 1.4 MB (1.8 MB uncompressed)
10 | * Color, pin and label your notes for quick organisation
11 | * Complement your notes with pictures (JPG, PNG, WEBP)
12 | * Export notes as TXT, JSON, HTML or PDF files with formatting
13 | * Create rich text notes with support for bold, italics, mono space and strike-through
14 | * Add clickable links to notes with support for phone numbers, email addresses and web urls
15 |
16 | [
](https://play.google.com/store/apps/details?id=com.omgodse.notally)
17 | [
](https://f-droid.org/packages/com.omgodse.notally/)
18 |
19 | ### Translations
20 | All translations are crowd sourced. To contribute, follow these [guidelines](https://m2.material.io/design/communication/writing.html) and email me or open a pull request.
21 |
22 | 

23 |
24 | ### Hall of fame
25 | * [Top 20 Android Apps 2021!](https://www.youtube.com/watch?v=bwz13aM0qJk)
26 | * [De-Googling Any Android Phone! (Google Apps Alternatives)](https://www.youtube.com/watch?v=RQUEgwgV99I)
27 | * [The BEST Private Notetaking Apps Explained](https://www.youtube.com/watch?v=BJw5tKPP1PY)
28 | * [Notally](https://www.noteapps.ca/notally/)
29 | * [The 9 Best Simple Note-Taking Apps for Android](https://www.makeuseof.com/simple-note-apps-android/)
30 |
31 | ### Copycats
32 | Clones of Notally keep popping up on the Play Store. They are not licensed under GPL3 and usually change a few colors, include ads, etc. Please [report them](https://support.google.com/googleplay/android-developer/contact/takedown) and [inform me](mailto:omgodseapps@gmail.com) if you find a new one.
33 |
34 | * https://play.google.com/store/apps/details?id=com.sladjan.notes
35 | * https://play.google.com/store/apps/details?id=com.sladjan.notespro
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2 |
3 | plugins {
4 | id 'com.android.application'
5 | id 'kotlin-android'
6 | id 'kotlin-parcelize'
7 | id 'com.google.devtools.ksp'
8 | }
9 |
10 | android {
11 | compileSdk 34
12 | namespace 'com.omgodse.notally'
13 |
14 | defaultConfig {
15 | applicationId 'com.omgodse.notally'
16 | minSdk 21
17 | targetSdk 34
18 | versionCode 56
19 | versionName "6.1"
20 | resourceConfigurations += ['en', 'ca', 'cs', 'da', 'de', 'el', 'es', 'fr', 'hu', 'in', 'it', 'ja', 'ko', 'my', 'nb', 'nl', 'nn', 'pl', 'pt-rBR', 'pt-rPT', 'ro', 'ru', 'sk', 'sl', 'sv', 'tl', 'tr', 'uk', 'vi', 'zh-rCN']
21 | vectorDrawables.generatedDensities = []
22 | }
23 |
24 | ksp {
25 | arg("room.generateKotlin", "true")
26 | arg("room.schemaLocation", "$projectDir/schemas")
27 | }
28 |
29 | buildTypes {
30 | debug {
31 | applicationIdSuffix ".debug"
32 | }
33 | release {
34 | crunchPngs false
35 | minifyEnabled true
36 | shrinkResources true
37 | proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
38 | }
39 | }
40 |
41 | kotlinOptions { jvmTarget = "1.8" }
42 |
43 | buildFeatures {
44 | viewBinding true
45 | buildConfig true
46 | }
47 |
48 | packagingOptions.resources {
49 | excludes += ["DebugProbesKt.bin", "META-INF/**.version", "kotlin/**.kotlin_builtins", "kotlin-tooling-metadata.json"]
50 | }
51 |
52 | androidResources {
53 | generateLocaleConfig = true
54 | }
55 | }
56 |
57 | tasks.withType(KotlinCompile).configureEach {
58 | kotlinOptions.jvmTarget = "1.8"
59 | }
60 |
61 | dependencies {
62 | final def navVersion = "2.3.5"
63 | final def roomVersion = "2.6.1"
64 |
65 | ksp "androidx.room:room-compiler:$roomVersion"
66 | implementation "androidx.room:room-ktx:$roomVersion"
67 | implementation "androidx.room:room-runtime:$roomVersion"
68 |
69 | implementation "androidx.work:work-runtime:2.9.0"
70 |
71 | implementation "androidx.navigation:navigation-ui-ktx:$navVersion"
72 | implementation "androidx.navigation:navigation-fragment-ktx:$navVersion"
73 |
74 | implementation "org.ocpsoft.prettytime:prettytime:4.0.6.Final"
75 | implementation "com.google.android.material:material:1.4.0"
76 |
77 | implementation "com.github.bumptech.glide:glide:4.15.1"
78 | implementation "com.github.rambler-digital-solutions:swipe-layout-android:1.0.17"
79 | implementation "com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0"
80 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | -keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | -renamesourcefileattribute SourceFile
22 | -keep class ** extends androidx.navigation.Navigator
23 | -keep class ** implements org.ocpsoft.prettytime.TimeUnit
--------------------------------------------------------------------------------
/app/src/main/java/android/print/PostPDFGenerator.kt:
--------------------------------------------------------------------------------
1 | package android.print
2 |
3 | import android.content.Context
4 | import android.os.ParcelFileDescriptor
5 | import android.webkit.WebView
6 | import android.webkit.WebViewClient
7 | import java.io.File
8 |
9 | /**
10 | * This class needs to be in android.print package to access the package private
11 | * methods of [PrintDocumentAdapter]
12 | */
13 | object PostPDFGenerator {
14 |
15 | fun create(file: File, content: String, context: Context, onResult: OnResult) {
16 | val webView = WebView(context)
17 | webView.loadDataWithBaseURL(null, content, "text/html", "utf-8", null)
18 | webView.webViewClient = object : WebViewClient() {
19 |
20 | override fun onPageFinished(view: WebView?, url: String?) {
21 | val adapter = webView.createPrintDocumentAdapter(file.nameWithoutExtension)
22 | print(file, adapter, onResult)
23 | }
24 | }
25 | }
26 |
27 |
28 | private fun print(file: File, adapter: PrintDocumentAdapter, onResult: OnResult) {
29 | val onLayoutResult = object : PrintDocumentAdapter.LayoutResultCallback() {
30 |
31 | override fun onLayoutFailed(error: CharSequence?) {
32 | onResult.onFailure(error)
33 | }
34 |
35 | override fun onLayoutFinished(info: PrintDocumentInfo?, changed: Boolean) {
36 | writeToFile(file, adapter, onResult)
37 | }
38 | }
39 |
40 | adapter.onLayout(null, getPrintAttributes(), null, onLayoutResult, null)
41 | }
42 |
43 | private fun writeToFile(file: File, adapter: PrintDocumentAdapter, onResult: OnResult) {
44 | val onWriteResult = object : PrintDocumentAdapter.WriteResultCallback() {
45 |
46 | override fun onWriteFailed(error: CharSequence?) {
47 | onResult.onFailure(error)
48 | }
49 |
50 | override fun onWriteFinished(pages: Array?) {
51 | onResult.onSuccess(file)
52 | }
53 | }
54 |
55 | val pages = arrayOf(PageRange.ALL_PAGES)
56 | if (!file.exists()) {
57 | file.createNewFile()
58 | }
59 | val fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_WRITE)
60 | adapter.onWrite(pages, fileDescriptor, null, onWriteResult)
61 | }
62 |
63 |
64 | private fun getPrintAttributes(): PrintAttributes {
65 | val builder = PrintAttributes.Builder()
66 | builder.setMediaSize(PrintAttributes.MediaSize.ISO_A4)
67 | builder.setMinMargins(PrintAttributes.Margins(250, 250, 250, 250))
68 | builder.setResolution(PrintAttributes.Resolution("Standard", "Standard", 100, 100))
69 | return builder.build()
70 | }
71 |
72 | interface OnResult {
73 |
74 | fun onSuccess(file: File)
75 |
76 | fun onFailure(message: CharSequence?)
77 | }
78 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/ActionMode.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import com.omgodse.notally.image.Event
5 | import com.omgodse.notally.preferences.BetterLiveData
6 | import com.omgodse.notally.room.BaseNote
7 |
8 | class ActionMode {
9 |
10 | val enabled = BetterLiveData(false)
11 | val count = BetterLiveData(0)
12 | val selectedNotes = HashMap()
13 | val selectedIds = selectedNotes.keys
14 | val closeListener = MutableLiveData>>()
15 |
16 | private fun refresh() {
17 | count.value = selectedNotes.size
18 | enabled.value = selectedNotes.size != 0
19 | }
20 |
21 | fun add(id: Long, baseNote: BaseNote) {
22 | selectedNotes[id] = baseNote
23 | refresh()
24 | }
25 |
26 | fun remove(id: Long) {
27 | selectedNotes.remove(id)
28 | refresh()
29 | }
30 |
31 | fun close(notify: Boolean) {
32 | val previous = HashSet(selectedIds)
33 | selectedNotes.clear()
34 | refresh()
35 | if (notify && selectedNotes.size == 0) {
36 | closeListener.value = Event(previous)
37 | }
38 | }
39 |
40 | fun isEnabled() = enabled.value
41 |
42 | // We assume selectedNotes.size is 1
43 | fun getFirstNote() = selectedNotes.values.first()
44 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/AutoBackupWorker.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.net.Uri
6 | import androidx.documentfile.provider.DocumentFile
7 | import androidx.work.Worker
8 | import androidx.work.WorkerParameters
9 | import com.omgodse.notally.miscellaneous.Export
10 | import com.omgodse.notally.miscellaneous.IO
11 | import com.omgodse.notally.miscellaneous.Operations
12 | import com.omgodse.notally.preferences.AutoBackup
13 | import com.omgodse.notally.preferences.Preferences
14 | import com.omgodse.notally.room.Converters
15 | import com.omgodse.notally.room.NotallyDatabase
16 | import java.text.SimpleDateFormat
17 | import java.util.Locale
18 | import java.util.zip.ZipOutputStream
19 |
20 | class AutoBackupWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) {
21 |
22 | override fun doWork(): Result {
23 | val app = context.applicationContext as Application
24 | val preferences = Preferences.getInstance(app)
25 | val backupPath = preferences.autoBackup.value
26 |
27 | if (backupPath != AutoBackup.emptyPath) {
28 | val uri = Uri.parse(backupPath)
29 | val folder = requireNotNull(DocumentFile.fromTreeUri(app, uri))
30 |
31 | if (folder.exists()) {
32 | val formatter = SimpleDateFormat("yyyyMMdd HHmmss '(Notally Backup)'", Locale.ENGLISH)
33 | val name = formatter.format(System.currentTimeMillis())
34 | val file = requireNotNull(folder.createFile("application/zip", name))
35 | val outputStream = requireNotNull(app.contentResolver.openOutputStream(file.uri))
36 |
37 | val zipStream = ZipOutputStream(outputStream)
38 |
39 | val database = NotallyDatabase.getDatabase(app)
40 | database.checkpoint()
41 |
42 | Export.backupDatabase(app, zipStream)
43 |
44 | val imageRoot = IO.getExternalImagesDirectory(app)
45 | val audioRoot = IO.getExternalAudioDirectory(app)
46 | database.getBaseNoteDao().getAllImages()
47 | .asSequence()
48 | .flatMap { string -> Converters.jsonToImages(string) }
49 | .forEach { image ->
50 | try {
51 | Export.backupFile(zipStream, imageRoot, "Images", image.name)
52 | } catch (exception: Exception) {
53 | Operations.log(app, exception)
54 | }
55 | }
56 | database.getBaseNoteDao().getAllAudios()
57 | .asSequence()
58 | .flatMap { string -> Converters.jsonToAudios(string) }
59 | .forEach { audio ->
60 | try {
61 | Export.backupFile(zipStream, audioRoot, "Audios", audio.name)
62 | } catch (exception: Exception) {
63 | Operations.log(app, exception)
64 | }
65 | }
66 |
67 | zipStream.close()
68 | }
69 | }
70 |
71 | return Result.success()
72 | }
73 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/Cache.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally
2 |
3 | import com.omgodse.notally.room.BaseNote
4 |
5 | object Cache {
6 |
7 | var list: List = ArrayList()
8 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/MenuDialog.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally
2 |
3 | import android.content.Context
4 | import android.view.ViewGroup.LayoutParams
5 | import android.widget.LinearLayout
6 | import androidx.core.widget.NestedScrollView
7 | import com.google.android.material.bottomsheet.BottomSheetDialog
8 | import com.omgodse.notally.databinding.MenuItemBinding
9 |
10 | class MenuDialog(context: Context) : BottomSheetDialog(context) {
11 |
12 | private val linearLayout: LinearLayout
13 |
14 | init {
15 | val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
16 |
17 | linearLayout = LinearLayout(context)
18 | linearLayout.layoutParams = params
19 | linearLayout.orientation = LinearLayout.VERTICAL
20 |
21 | val scrollView = NestedScrollView(context)
22 | scrollView.layoutParams = params
23 |
24 | scrollView.addView(linearLayout)
25 | setContentView(scrollView)
26 | }
27 |
28 | fun add(title: Int, onClick: () -> Unit): MenuDialog {
29 | val item = MenuItemBinding.inflate(layoutInflater, linearLayout, true).root
30 | item.setText(title)
31 | item.setOnClickListener {
32 | dismiss()
33 | onClick()
34 | }
35 | return this
36 | }
37 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/NotallyApplication.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally
2 |
3 | import android.app.Application
4 | import androidx.appcompat.app.AppCompatDelegate
5 | import androidx.work.ExistingPeriodicWorkPolicy
6 | import androidx.work.PeriodicWorkRequest
7 | import androidx.work.WorkManager
8 | import com.omgodse.notally.preferences.Preferences
9 | import com.omgodse.notally.preferences.Theme
10 | import java.util.concurrent.TimeUnit
11 |
12 | class NotallyApplication : Application() {
13 |
14 | override fun onCreate() {
15 | super.onCreate()
16 |
17 | val preferences = Preferences.getInstance(this)
18 | preferences.theme.observeForever { theme ->
19 | when (theme) {
20 | Theme.dark -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
21 | Theme.light -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
22 | Theme.followSystem -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
23 | }
24 | }
25 |
26 | val request = PeriodicWorkRequest.Builder(AutoBackupWorker::class.java, 12, TimeUnit.HOURS).build()
27 | WorkManager.getInstance(this).enqueueUniquePeriodicWork("Auto Backup", ExistingPeriodicWorkPolicy.KEEP, request)
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/OverflowEditText.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.appcompat.widget.AppCompatEditText
6 |
7 | /**
8 | * Implementation that fixes a bug in Lollipop where clicking on the overflow icon
9 | * in the custom text selection mode causes it to end.
10 | * For more information, see this -> https://issuetracker.google.com/issues/36937508
11 | */
12 | class OverflowEditText(context: Context, attrs: AttributeSet) : AppCompatEditText(context, attrs) {
13 |
14 | var isActionModeOn = false
15 |
16 | override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
17 | if (!isActionModeOn) {
18 | super.onWindowFocusChanged(hasWindowFocus)
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/Progress.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally
2 |
3 | class Progress(val inProgress: Boolean, val current: Int, val total: Int, val indeterminate: Boolean)
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/activities/MakeList.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.activities
2 |
3 | import android.view.inputmethod.InputMethodManager
4 | import com.omgodse.notally.miscellaneous.setOnNextAction
5 | import com.omgodse.notally.recyclerview.ListItemListener
6 | import com.omgodse.notally.recyclerview.adapter.MakeListAdapter
7 | import com.omgodse.notally.recyclerview.viewholder.MakeListVH
8 | import com.omgodse.notally.room.ListItem
9 | import com.omgodse.notally.room.Type
10 |
11 | class MakeList : NotallyActivity(Type.LIST) {
12 |
13 | private lateinit var adapter: MakeListAdapter
14 |
15 | override fun configureUI() {
16 | binding.EnterTitle.setOnNextAction {
17 | moveToNext(-1)
18 | }
19 |
20 | if (model.isNewNote) {
21 | if (model.items.isEmpty()) {
22 | addListItem()
23 | }
24 | }
25 | }
26 |
27 |
28 | override fun setupListeners() {
29 | super.setupListeners()
30 | binding.AddItem.setOnClickListener {
31 | addListItem()
32 | }
33 | }
34 |
35 | override fun setStateFromModel() {
36 | super.setStateFromModel()
37 | val elevation = resources.displayMetrics.density * 2
38 |
39 | adapter = MakeListAdapter(model.textSize, elevation, model.items, object : ListItemListener {
40 |
41 | override fun delete(position: Int) {
42 | model.items.removeAt(position)
43 | adapter.notifyItemRemoved(position)
44 | }
45 |
46 | override fun moveToNext(position: Int) {
47 | this@MakeList.moveToNext(position)
48 | }
49 |
50 | override fun textChanged(position: Int, text: String) {
51 | model.items[position].body = text
52 | }
53 |
54 | override fun checkedChanged(position: Int, checked: Boolean) {
55 | model.items[position].checked = checked
56 | }
57 | })
58 |
59 | binding.RecyclerView.adapter = adapter
60 | }
61 |
62 |
63 | private fun addListItem() {
64 | val position = model.items.size
65 | val listItem = ListItem(String(), false)
66 | model.items.add(listItem)
67 | adapter.notifyItemInserted(position)
68 | val inputManager = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
69 | binding.RecyclerView.post {
70 | val viewHolder = binding.RecyclerView.findViewHolderForAdapterPosition(position) as MakeListVH?
71 | if (viewHolder != null) {
72 | viewHolder.binding.EditText.requestFocus()
73 | inputManager.showSoftInput(viewHolder.binding.EditText, InputMethodManager.SHOW_IMPLICIT)
74 | }
75 | }
76 | }
77 |
78 | private fun moveToNext(currentPosition: Int) {
79 | val viewHolder = binding.RecyclerView.findViewHolderForAdapterPosition(currentPosition + 1) as MakeListVH?
80 | if (viewHolder != null) {
81 | if (viewHolder.binding.CheckBox.isChecked) {
82 | moveToNext(currentPosition + 1)
83 | } else viewHolder.binding.EditText.requestFocus()
84 | } else addListItem()
85 | }
86 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/audio/AudioPlayService.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.audio
2 |
3 | import android.app.Service
4 | import android.content.Intent
5 | import android.media.MediaPlayer
6 | import android.os.IBinder
7 | import com.omgodse.notally.miscellaneous.IO
8 | import com.omgodse.notally.miscellaneous.Operations
9 | import com.omgodse.notally.room.Audio
10 | import java.io.File
11 |
12 | class AudioPlayService : Service() {
13 |
14 | private var state = IDLE
15 | private var stateBeforeSeeking = -1
16 | private var errorType = 0
17 | private var errorCode = 0
18 | private lateinit var player: MediaPlayer
19 |
20 | var onStateChange: (() -> Unit)? = null
21 |
22 | override fun onCreate() {
23 | super.onCreate()
24 | player = MediaPlayer()
25 | player.setOnPreparedListener { setState(PREPARED) }
26 | player.setOnCompletionListener { setState(COMPLETED) }
27 | player.setOnSeekCompleteListener { setState(stateBeforeSeeking) }
28 | player.setOnErrorListener { _, what, extra ->
29 | errorType = what
30 | errorCode = extra
31 | setState(ERROR)
32 | return@setOnErrorListener true
33 | }
34 | }
35 |
36 | override fun onDestroy() {
37 | super.onDestroy()
38 | player.release()
39 | }
40 |
41 | override fun onBind(intent: Intent?): IBinder {
42 | return LocalBinder(this)
43 | }
44 |
45 |
46 | fun initialise(audio: Audio) {
47 | if (state == IDLE) {
48 | val audioRoot = IO.getExternalAudioDirectory(application)
49 | if (audioRoot != null) {
50 | try {
51 | val file = File(audioRoot, audio.name)
52 | player.setDataSource(file.absolutePath)
53 | setState(INITIALISED)
54 | player.prepareAsync()
55 | } catch (exception: Exception) {
56 | setIOError()
57 | Operations.log(application, exception)
58 | }
59 | } else setIOError()
60 | }
61 | }
62 |
63 | fun play() {
64 | when (state) {
65 | PREPARED, PAUSED, COMPLETED -> {
66 | player.start()
67 | setState(STARTED)
68 | }
69 | STARTED -> {
70 | player.pause()
71 | setState(PAUSED)
72 | }
73 | }
74 | }
75 |
76 | fun seek(milliseconds: Long) {
77 | if (state == PREPARED || state == STARTED || state == PAUSED || state == COMPLETED) {
78 | stateBeforeSeeking = state
79 | player.seekTo(milliseconds.toInt())
80 | setState(SEEKING)
81 | }
82 | }
83 |
84 |
85 | fun getState(): Int {
86 | return state
87 | }
88 |
89 | fun getErrorType(): Int {
90 | return errorType
91 | }
92 |
93 | fun getErrorCode(): Int {
94 | return errorCode
95 | }
96 |
97 | fun getCurrentPosition(): Int {
98 | return if (state != IDLE && state != ERROR) player.currentPosition else 0
99 | }
100 |
101 |
102 | private fun setState(state: Int) {
103 | this.state = state
104 | onStateChange?.invoke()
105 | }
106 |
107 | private fun setIOError() {
108 | errorCode = 0
109 | errorType = 15
110 | setState(ERROR)
111 | }
112 |
113 | companion object {
114 | const val IDLE = 0
115 | const val INITIALISED = 1
116 | const val PREPARED = 2
117 | const val STARTED = 3
118 | const val PAUSED = 4
119 | const val SEEKING = 5
120 | const val COMPLETED = 6
121 | const val ERROR = 7
122 | }
123 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/audio/LocalBinder.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.audio
2 |
3 | import android.app.Service
4 | import android.os.Binder
5 |
6 | class LocalBinder(private val service: T) : Binder() {
7 |
8 | fun getService(): T = service
9 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/audio/Status.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.audio
2 |
3 | enum class Status { READY, PAUSED, RECORDING }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/fragments/Archived.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.fragments
2 |
3 | import com.omgodse.notally.R
4 |
5 | class Archived : NotallyFragment() {
6 |
7 | override fun getBackground() = R.drawable.archive
8 |
9 | override fun getObservable() = model.archivedNotes
10 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/fragments/Deleted.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.fragments
2 |
3 | import android.view.Menu
4 | import android.view.MenuInflater
5 | import com.google.android.material.dialog.MaterialAlertDialogBuilder
6 | import com.omgodse.notally.R
7 | import com.omgodse.notally.miscellaneous.add
8 |
9 | class Deleted : NotallyFragment() {
10 |
11 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
12 | menu.add(R.string.delete_all, R.drawable.delete_all) { deleteAllNotes() }
13 | }
14 |
15 |
16 | private fun deleteAllNotes() {
17 | MaterialAlertDialogBuilder(requireContext())
18 | .setMessage(R.string.delete_all_notes)
19 | .setPositiveButton(R.string.delete) { _, _ -> model.deleteAllBaseNotes() }
20 | .setNegativeButton(R.string.cancel, null)
21 | .show()
22 | }
23 |
24 |
25 | override fun getBackground() = R.drawable.delete
26 |
27 | override fun getObservable() = model.deletedNotes
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/fragments/DisplayLabel.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.fragments
2 |
3 | import androidx.lifecycle.LiveData
4 | import com.omgodse.notally.R
5 | import com.omgodse.notally.miscellaneous.Constants
6 | import com.omgodse.notally.room.Item
7 |
8 | class DisplayLabel : NotallyFragment() {
9 |
10 | override fun getBackground() = R.drawable.label
11 |
12 | override fun getObservable(): LiveData> {
13 | val label = requireNotNull(requireArguments().getString(Constants.SelectedLabel))
14 | return model.getNotesByLabel(label)
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/fragments/Notes.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.fragments
2 |
3 | import android.view.Menu
4 | import android.view.MenuInflater
5 | import androidx.navigation.fragment.findNavController
6 | import com.omgodse.notally.R
7 | import com.omgodse.notally.miscellaneous.add
8 |
9 | class Notes : NotallyFragment() {
10 |
11 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
12 | menu.add(R.string.search, R.drawable.search) { findNavController().navigate(R.id.NotesToSearch) }
13 | }
14 |
15 |
16 | override fun getObservable() = model.baseNotes
17 |
18 | override fun getBackground() = R.drawable.notebook
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/fragments/Search.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.fragments
2 |
3 | import android.os.Build
4 | import android.os.Bundle
5 | import android.view.View
6 | import com.omgodse.notally.R
7 | import com.omgodse.notally.room.Folder
8 |
9 | class Search : NotallyFragment() {
10 |
11 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
12 | binding?.ChipGroup?.visibility = View.VISIBLE
13 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
14 | binding?.RecyclerView?.scrollIndicators = View.SCROLL_INDICATOR_TOP
15 | }
16 | super.onViewCreated(view, savedInstanceState)
17 |
18 | val checked = when (model.folder) {
19 | Folder.NOTES -> R.id.Notes
20 | Folder.DELETED -> R.id.Deleted
21 | Folder.ARCHIVED -> R.id.Archived
22 | }
23 |
24 | binding?.ChipGroup?.check(checked)
25 |
26 | binding?.ChipGroup?.setOnCheckedChangeListener { _, checkedId ->
27 | when (checkedId) {
28 | R.id.Notes -> model.folder = Folder.NOTES
29 | R.id.Deleted -> model.folder = Folder.DELETED
30 | R.id.Archived -> model.folder = Folder.ARCHIVED
31 | }
32 | }
33 | }
34 |
35 |
36 | override fun getBackground() = R.drawable.search
37 |
38 | override fun getObservable() = model.searchResults
39 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/image/AspectRatioRecyclerView.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.image
2 |
3 | import android.content.Context
4 | import android.util.AttributeSet
5 | import androidx.recyclerview.widget.RecyclerView
6 |
7 | class AspectRatioRecyclerView(context: Context, attrs: AttributeSet) : RecyclerView(context, attrs) {
8 |
9 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
10 | super.onMeasure(widthMeasureSpec, heightMeasureSpec)
11 | val height = (measuredWidth * 0.75).toInt()
12 | if (height != measuredHeight) {
13 | setMeasuredDimension(measuredWidth, height)
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/image/Event.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.image
2 |
3 | class Event(val data: T) {
4 |
5 | private var isHandled = false
6 |
7 | fun handle(function: (data: T) -> Unit) {
8 | if (!isHandled) {
9 | function(data)
10 | isHandled = true
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/image/ImageError.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.image
2 |
3 | class ImageError (val name: String, val description: String)
--------------------------------------------------------------------------------
/app/src/main/java/com/omgodse/notally/legacy/Migrations.kt:
--------------------------------------------------------------------------------
1 | package com.omgodse.notally.legacy
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.content.SharedPreferences
6 | import com.omgodse.notally.room.BaseNote
7 | import com.omgodse.notally.room.Folder
8 | import com.omgodse.notally.room.Label
9 | import java.io.File
10 |
11 | // Backwards compatibility from v3.2 to v3.3
12 | object Migrations {
13 |
14 | fun clearAllLabels(app: Application) {
15 | val preferences = getLabelsPreferences(app)
16 | preferences.edit().clear().commit()
17 | }
18 |
19 | fun clearAllFolders(app: Application) {
20 | getNotePath(app).listFiles()?.forEach { file -> file.delete() }
21 | getDeletedPath(app).listFiles()?.forEach { file -> file.delete() }
22 | getArchivedPath(app).listFiles()?.forEach { file -> file.delete() }
23 | }
24 |
25 | fun getPreviousLabels(app: Application): List