Skip to content
This repository was archived by the owner on Sep 2, 2024. It is now read-only.

Commit 25e84e8

Browse files
committed
Merge branch 'develop'
2 parents 222a9fb + da8f6cd commit 25e84e8

File tree

67 files changed

+2533
-147
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

67 files changed

+2533
-147
lines changed

.github/CONTRIBUTING.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
|--------------|---------------|----------|
1111
| English | [@msoultanidis](https://github.com/msoultanidis) | Complete |
1212
| Greek (`el`) | [@msoultanidis](https://github.com/msoultanidis) | Complete |
13+
| Polish (`pl`) | [@TheDidek](https://github.com/TheDidek) | Complete |
14+
| Brazilian Portuguese (`pt-rBR`) | [@RodolfoCandido](https://github.com/RodolfoCandido) | Complete |
15+
| Italian (`it`) | [@danigarau](https://github.com/danigarau) | Complete |
16+
| French (`fr`) | [@locness3](https://github.com/locness3) | Complete |
17+
| Spanish (`es`) | [@urizev](https://github.com/urizev) | Complete |
1318

1419
You can help Quillnote grow by translating it in languages it does not support yet or by improving existing translations.
1520

app/build.gradle

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ android {
1616
applicationId "org.qosp.notes"
1717
minSdkVersion 21
1818
targetSdkVersion 30
19-
versionCode 3
20-
versionName "1.2.0"
19+
versionCode 4
20+
versionName "1.3.0"
2121

2222
testInstrumentationRunner "org.qosp.notes.TestRunner"
2323
}
@@ -30,6 +30,18 @@ android {
3030
}
3131
}
3232

33+
flavorDimensions "versions"
34+
productFlavors {
35+
googleFlavor {
36+
dimension "versions"
37+
buildConfigField "boolean", "IS_GOOGLE", "true"
38+
}
39+
defaultFlavor {
40+
dimension "versions"
41+
buildConfigField "boolean", "IS_GOOGLE", "false"
42+
}
43+
}
44+
3345
kotlinOptions {
3446
jvmTarget = "1.8"
3547
}
@@ -104,7 +116,7 @@ dependencies {
104116
implementation "io.noties.markwon:linkify:$markwon_version"
105117
implementation "io.noties.markwon:ext-strikethrough:$markwon_version"
106118
implementation "io.noties.markwon:ext-tables:$markwon_version"
107-
implementation "io.noties.markwon:image-coil:$markwon_version"
119+
implementation "io.noties.markwon:ext-tasklist:$markwon_version"
108120
implementation "me.saket:better-link-movement-method:2.2.0"
109121

110122
// Work Manager
@@ -135,6 +147,6 @@ dependencies {
135147
implementation "com.squareup.retrofit2:retrofit:$retrofit_version"
136148
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
137149

138-
// LeakCanary
139-
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanary_version"
150+
// LeakCanary
151+
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanary_version"
140152
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<string name="app_support_page" translatable="false"></string>
4+
</resources>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<resources>
2+
<string name="about_support" translatable="false"></string>
3+
<string name="about_support_subtext" translatable="false"></string>
4+
</resources>

app/src/main/java/org/qosp/notes/data/dao/NoteDao.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,16 @@ interface NoteDao {
148148
}
149149
return Pair(column, order)
150150
}
151+
152+
fun getNotesWithoutNotebook(sortMethod: SortMethod): Flow<List<Note>> {
153+
val (column, order) = getOrderByMethod(sortMethod)
154+
return rawGetQuery(
155+
SimpleSQLiteQuery(
156+
"""
157+
SELECT * FROM notes WHERE isArchived = 0 AND isDeleted = 0 AND notebookId IS NULL
158+
ORDER BY isPinned DESC, $column $order
159+
"""
160+
)
161+
)
162+
}
151163
}

app/src/main/java/org/qosp/notes/data/repo/NoteRepository.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ class NoteRepository(
182182
return noteDao.getNonRemoteNotes(sortMethod, provider)
183183
}
184184

185+
fun getNotesWithoutNotebook(sortMethod: SortMethod = defaultOf()): Flow<List<Note>> {
186+
return noteDao.getNotesWithoutNotebook(sortMethod)
187+
}
188+
185189
suspend fun moveRemotelyDeletedNotesToBin(idsInUse: List<Long>, provider: CloudService) {
186190
noteDao.moveRemotelyDeletedNotesToBin(idsInUse, provider)
187191
}

app/src/main/java/org/qosp/notes/data/sync/core/ProviderConfig.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ interface ProviderConfig {
66
val remoteAddress: String
77
val username: String
88
val provider: CloudService
9+
val authenticationHeaders: Map<String, String>
910
}

app/src/main/java/org/qosp/notes/data/sync/core/SyncManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ class SyncManager(
3333
}
3434

3535
val config = prefs.map { prefs -> prefs.config }
36+
.stateIn(syncingScope, SharingStarted.WhileSubscribed(5000), null)
3637

3738
@OptIn(ObsoleteCoroutinesApi::class)
3839
private val actor = syncingScope.actor<Message> {

app/src/main/java/org/qosp/notes/data/sync/nextcloud/NextcloudConfig.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ data class NextcloudConfig(
1616
) : ProviderConfig {
1717

1818
val credentials = ("Basic " + Base64.encodeToString("$username:$password".toByteArray(), Base64.NO_WRAP)).trim()
19+
1920
override val provider: CloudService = CloudService.NEXTCLOUD
21+
override val authenticationHeaders: Map<String, String>
22+
get() = mapOf("Authorization" to credentials)
2023

2124
companion object {
2225
@OptIn(ExperimentalCoroutinesApi::class)

app/src/main/java/org/qosp/notes/di/MarkwonModule.kt

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,31 @@ import dagger.Module
66
import dagger.Provides
77
import dagger.hilt.InstallIn
88
import dagger.hilt.android.components.FragmentComponent
9-
import dagger.hilt.android.qualifiers.ApplicationContext
9+
import dagger.hilt.android.qualifiers.ActivityContext
1010
import dagger.hilt.android.scopes.FragmentScoped
1111
import io.noties.markwon.*
1212
import io.noties.markwon.editor.MarkwonEditor
1313
import io.noties.markwon.editor.handler.EmphasisEditHandler
1414
import io.noties.markwon.editor.handler.StrongEmphasisEditHandler
1515
import io.noties.markwon.ext.strikethrough.StrikethroughPlugin
1616
import io.noties.markwon.ext.tables.TablePlugin
17-
import io.noties.markwon.image.coil.CoilImagesPlugin
17+
import io.noties.markwon.ext.tasklist.TaskListPlugin
1818
import io.noties.markwon.linkify.LinkifyPlugin
1919
import io.noties.markwon.movement.MovementMethodPlugin
2020
import me.saket.bettermovementmethod.BetterLinkMovementMethod
21+
import org.qosp.notes.R
22+
import org.qosp.notes.data.sync.core.SyncManager
2123
import org.qosp.notes.ui.editor.markdown.*
22-
import javax.inject.Named
24+
import org.qosp.notes.ui.utils.coil.CoilImagesPlugin
25+
import org.qosp.notes.ui.utils.resolveAttribute
2326

2427
@Module
2528
@InstallIn(FragmentComponent::class)
2629
object MarkwonModule {
27-
const val SUPPORTS_IMAGES = "SUPPORTS_IMAGES"
2830

2931
@Provides
3032
@FragmentScoped
31-
fun provideBaseMarkwonBuilder(@ApplicationContext context: Context): Markwon.Builder {
33+
fun provideMarkwon(@ActivityContext context: Context, syncManager: SyncManager): Markwon {
3234
return Markwon.builder(context)
3335
.usePlugin(LinkifyPlugin.create(Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS))
3436
.usePlugin(SoftBreakAddsNewLinePlugin.create())
@@ -40,26 +42,15 @@ object MarkwonModule {
4042
builder.linkResolver(LinkResolverDef())
4143
}
4244
})
43-
}
44-
45-
@Provides
46-
@Named(SUPPORTS_IMAGES)
47-
@FragmentScoped
48-
fun provideMarkwonInstanceWithImageSupport(
49-
@ApplicationContext context: Context,
50-
builder: Markwon.Builder
51-
): Markwon {
52-
return builder
53-
.usePlugin(CoilImagesPlugin.create(context))
45+
.usePlugin(CoilImagesPlugin.create(context, syncManager))
46+
.apply {
47+
val mainColor = context.resolveAttribute(R.attr.colorMarkdownTask) ?: return@apply
48+
val backgroundColor = context.resolveAttribute(R.attr.colorBackground) ?: return@apply
49+
usePlugin(TaskListPlugin.create(mainColor, mainColor, backgroundColor))
50+
}
5451
.build()
5552
}
5653

57-
@Provides
58-
@FragmentScoped
59-
fun provideBaseMarkwonInstance(builder: Markwon.Builder): Markwon {
60-
return builder.build()
61-
}
62-
6354
@Provides
6455
@FragmentScoped
6556
fun provideMarkwonEditor(markwon: Markwon): MarkwonEditor {

app/src/main/java/org/qosp/notes/preferences/AppPreferences.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ data class AppPreferences(
1212
val dateFormat: DateFormat = defaultOf(),
1313
val timeFormat: TimeFormat = defaultOf(),
1414
val openMediaIn: OpenMediaIn = defaultOf(),
15+
val showDate: ShowDate = defaultOf(),
16+
val groupNotesWithoutNotebook: GroupNotesWithoutNotebook = defaultOf(),
1517
val cloudService: CloudService = defaultOf(),
1618
val syncMode: SyncMode = defaultOf(),
1719
val backgroundSync: BackgroundSync = defaultOf(),

app/src/main/java/org/qosp/notes/preferences/PreferenceEnums.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,18 @@ enum class OpenMediaIn(override val nameResource: Int) : HasNameResource, EnumPr
7272
EXTERNAL(R.string.preferences_open_media_in_external),
7373
}
7474

75+
enum class ShowDate(override val nameResource: Int) : HasNameResource, EnumPreference by key("show_date") {
76+
YES(R.string.yes) { override val isDefault = true },
77+
NO(R.string.no),
78+
}
79+
80+
enum class GroupNotesWithoutNotebook(
81+
override val nameResource: Int,
82+
) : HasNameResource, EnumPreference by key("group_notes_without_notebook") {
83+
YES(R.string.yes),
84+
NO(R.string.no) { override val isDefault = true },
85+
}
86+
7587
enum class CloudService(override val nameResource: Int) : HasNameResource, EnumPreference by key("cloud_service") {
7688
DISABLED(R.string.preferences_cloud_service_disabled) { override val isDefault = true },
7789
NEXTCLOUD(R.string.preferences_cloud_service_nextcloud),

app/src/main/java/org/qosp/notes/preferences/PreferenceRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class PreferenceRepository(
4040
dateFormat = prefs.getEnum(),
4141
timeFormat = prefs.getEnum(),
4242
openMediaIn = prefs.getEnum(),
43+
showDate = prefs.getEnum(),
44+
groupNotesWithoutNotebook = prefs.getEnum(),
4345
cloudService = prefs.getEnum(),
4446
syncMode = prefs.getEnum(),
4547
backgroundSync = prefs.getEnum(),

app/src/main/java/org/qosp/notes/ui/ActivityViewModel.kt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import kotlinx.coroutines.*
88
import kotlinx.coroutines.flow.SharingStarted
99
import kotlinx.coroutines.flow.StateFlow
1010
import kotlinx.coroutines.flow.first
11+
import kotlinx.coroutines.flow.flatMapLatest
12+
import kotlinx.coroutines.flow.map
1113
import kotlinx.coroutines.flow.stateIn
14+
import me.msoul.datastore.defaultOf
1215
import org.qosp.notes.components.MediaStorageManager
1316
import org.qosp.notes.data.model.Note
1417
import org.qosp.notes.data.model.Notebook
@@ -35,8 +38,18 @@ class ActivityViewModel @Inject constructor(
3538
private val syncManager: SyncManager,
3639
) : ViewModel() {
3740

38-
val notebooks: StateFlow<List<Notebook>> = notebookRepository.getAll()
39-
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), listOf())
41+
@OptIn(ExperimentalCoroutinesApi::class)
42+
val notebooks: StateFlow<Pair<Boolean, List<Notebook>>> =
43+
preferenceRepository.get<GroupNotesWithoutNotebook>().flatMapLatest { groupNotesWithoutNotebook ->
44+
notebookRepository.getAll().map { notebooks ->
45+
(groupNotesWithoutNotebook == GroupNotesWithoutNotebook.YES) to notebooks
46+
}
47+
}
48+
.stateIn(
49+
scope = viewModelScope,
50+
started = SharingStarted.WhileSubscribed(5000),
51+
initialValue = (defaultOf<GroupNotesWithoutNotebook>() == GroupNotesWithoutNotebook.YES) to listOf(),
52+
)
4053

4154
var showHiddenNotes: Boolean = false
4255
var notesToBackup: Set<Note>? = null
@@ -134,6 +147,20 @@ class ActivityViewModel @Inject constructor(
134147
)
135148
}
136149

150+
fun disableMarkdown(vararg notes: Note) = update(*notes) { note ->
151+
note.copy(
152+
isMarkdownEnabled = false,
153+
modifiedDate = Instant.now().epochSecond,
154+
)
155+
}
156+
157+
fun enableMarkdown(vararg notes: Note) = update(*notes) { note ->
158+
note.copy(
159+
isMarkdownEnabled = true,
160+
modifiedDate = Instant.now().epochSecond,
161+
)
162+
}
163+
137164
fun duplicateNotes(vararg notes: Note) = notes.forEachAsync { note ->
138165
val oldId = note.id
139166
val cloned = note.copy(

app/src/main/java/org/qosp/notes/ui/MainActivity.kt

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class MainActivity : BaseActivity() {
125125
val textViewUsername = header.findViewById<AppCompatTextView>(R.id.text_view_username)
126126
val textViewProvider = header.findViewById<AppCompatTextView>(R.id.text_view_provider)
127127

128-
// Fixes bug that causes the header to have large padding when the keybord is open
128+
// Fixes bug that causes the header to have large padding when the keyboard is open
129129
ViewCompat.setOnApplyWindowInsetsListener(header) { view, insets ->
130130
header.setPadding(0, insets.getInsets(WindowInsetsCompat.Type.systemBars()).top, 0, 0)
131131
WindowInsetsCompat.CONSUMED
@@ -166,20 +166,35 @@ class MainActivity : BaseActivity() {
166166
}
167167
}
168168

169-
private fun setupDrawerMenuItemClickListeners() {
169+
private fun setupDrawerMenuItems() {
170170
// Alternative of setupWithNavController(), NavigationUI.java
171171
// Sets up click listeners for all drawer menu items except from notebooks.
172172
// Those are handled in createNotebookMenuItems()
173-
(topLevelMenu.children + notebooksMenu.children).forEach { item ->
174-
if (item.itemId !in primaryDestinations + secondaryDestinations) return@forEach
173+
(topLevelMenu.children + listOfNotNull(notebooksMenu.findItem(R.id.fragment_manage_notebooks)))
174+
.forEach { item ->
175+
if (item.itemId !in primaryDestinations + secondaryDestinations) return@forEach
175176

176-
item.setOnMenuItemClickListener {
177-
binding.drawer.closeAndThen {
178-
navController.navigateSafely(item.itemId)
177+
item.setOnMenuItemClickListener {
178+
binding.drawer.closeAndThen {
179+
navController.navigateSafely(item.itemId)
180+
}
181+
false // Returning true would cause the menu item to become checked.
182+
// We check the menu items only when the destination changes.
179183
}
180-
false // Returning true would cause the menu item to become checked.
181-
// We check the menu items only when the destination changes.
182184
}
185+
186+
notebooksMenu.findItem(R.id.nav_default_notebook)?.setOnMenuItemClickListener {
187+
binding.drawer.closeAndThen {
188+
navController.navigateSafely(
189+
R.id.fragment_notebook,
190+
bundleOf(
191+
"notebookId" to R.id.nav_default_notebook.toLong(),
192+
"notebookName" to getString(R.string.default_notebook),
193+
)
194+
)
195+
}
196+
false // Returning true would cause the menu item to become checked.
197+
// We check the menu items only when the destination changes.
183198
}
184199
}
185200

@@ -222,7 +237,7 @@ class MainActivity : BaseActivity() {
222237
navController =
223238
(supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
224239

225-
setupDrawerMenuItemClickListeners()
240+
setupDrawerMenuItems()
226241

227242
navController.addOnDestinationChangedListener { controller, destination, arguments ->
228243
currentFocus?.hideKeyboard()
@@ -231,8 +246,8 @@ class MainActivity : BaseActivity() {
231246
setDrawerEnabled(destination.id != R.id.fragment_editor)
232247
}
233248

234-
activityModel.notebooks.collect(this) { notebooks ->
235-
val notebookIds = notebooks.map { it.id.toInt() }.toSet()
249+
activityModel.notebooks.collect(this) { (showDefaultNotebook, notebooks) ->
250+
val notebookIds = (notebooks.map { it.id.toInt() } + R.id.nav_default_notebook).toSet()
236251

237252
// Remove deleted notebooks from the menu
238253
(primaryDestinations + secondaryDestinations + notebookIds).let { dests ->
@@ -244,6 +259,12 @@ class MainActivity : BaseActivity() {
244259
}
245260

246261
createNotebookMenuItems(notebooks)
262+
263+
val defaultTitle = getString(R.string.default_notebook)
264+
notebooksMenu.findItem(R.id.nav_default_notebook)?.apply {
265+
isVisible = showDefaultNotebook
266+
title = defaultTitle + " (${getString(R.string.default_string)})".takeIf { notebooks.any { it.name == defaultTitle } }.orEmpty()
267+
}
247268
}
248269
}
249270

0 commit comments

Comments
 (0)