diff --git a/app/build.gradle b/app/build.gradle index 5823addc..5643cbe8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,8 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' - id 'kotlin-kapt' id 'dagger.hilt.android.plugin' + id 'kotlin-kapt' } android { @@ -10,16 +10,17 @@ android { defaultConfig { applicationId "com.mhss.app.mybrain" + namespace "com.mhss.app.mybrain" minSdk 24 targetSdk 33 - versionCode 3 - versionName "1.0.2" + versionCode 4 + versionName "1.0.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary true } - resConfigs "en", "ar", "zh-rCN", "hi", "pl", "ru" + resConfigs "en", "ar", "zh-rCN", "hi", "pl", "ru", "ro", "pt-rBR", "de" kapt { arguments { arg("room.schemaLocation", "$projectDir/schemas") @@ -69,27 +70,28 @@ kotlin { } dependencies { - def nav_version = "2.5.1" def room_version = "2.4.3" def coroutines_version = "1.6.1" - implementation 'androidx.core:core-ktx:1.8.0' - implementation "androidx.compose.ui:ui:$compose_version" - implementation "androidx.compose.material:material:$compose_version" - implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation platform('androidx.compose:compose-bom:2022.10.00') + + implementation 'androidx.core:core-ktx:1.9.0' + implementation "androidx.compose.ui:ui" + implementation "androidx.compose.material:material" + implementation "androidx.compose.ui:ui-tooling-preview" implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' - implementation 'androidx.activity:activity-compose:1.5.1' + implementation 'androidx.activity:activity-compose:1.6.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' - androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" - debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + androidTestImplementation "androidx.compose.ui:ui-test-junit4" + debugImplementation "androidx.compose.ui:ui-tooling" // Compose navigation - implementation "androidx.navigation:navigation-compose:$nav_version" + implementation "androidx.navigation:navigation-compose:2.5.2" implementation 'androidx.hilt:hilt-navigation-compose:1.0.0' -// Room + // Room implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" implementation "androidx.room:room-ktx:$room_version" @@ -98,13 +100,14 @@ dependencies { implementation "com.google.dagger:hilt-android:2.43.2" kapt "com.google.dagger:hilt-android-compiler:2.43.2" kapt "androidx.hilt:hilt-compiler:1.0.0" + implementation 'androidx.hilt:hilt-work:1.0.0' // Coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" // Gson - implementation 'com.google.code.gson:gson:2.9.0' + implementation 'com.google.code.gson:gson:2.9.1' // Preferences DataStore implementation "androidx.datastore:datastore-preferences:1.0.0" @@ -118,8 +121,16 @@ dependencies { implementation 'com.github.jeziellago:compose-markdown:0.2.9' // Compose Glance (Widgets) - implementation "androidx.glance:glance-appwidget:1.0.0-alpha03" + implementation "androidx.glance:glance-appwidget:1.0.0-alpha05" + + //Moshi + implementation "com.squareup.moshi:moshi-kotlin:1.14.0" + + // WorkManager + implementation "androidx.work:work-runtime-ktx:2.7.1" + // Compose live data + implementation "androidx.compose.runtime:runtime-livedata" } kapt { diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 481bb434..16f03358 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -18,4 +18,5 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile +-dontobfuscate \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8c608968..ebdc311a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,6 @@ + xmlns:tools="http://schemas.android.com/tools"> @@ -9,23 +8,26 @@ + + + android:windowSoftInputMode="adjustResize"> @@ -95,8 +97,8 @@ + android:exported="true" + android:label="@string/calendar"> @@ -111,9 +113,10 @@ android:enabled="@bool/glance_appwidget_available" android:exported="true"> - - - + + + + @@ -121,8 +124,8 @@ + android:exported="false" + android:label="@string/tasks"> @@ -134,8 +137,14 @@ - + android:exported="false" /> + + + + \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/app/MyBrainApplication.kt b/app/src/main/java/com/mhss/app/mybrain/app/MyBrainApplication.kt index 4c61174d..a1d95f6d 100644 --- a/app/src/main/java/com/mhss/app/mybrain/app/MyBrainApplication.kt +++ b/app/src/main/java/com/mhss/app/mybrain/app/MyBrainApplication.kt @@ -9,19 +9,30 @@ import androidx.annotation.StringRes import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore +import androidx.hilt.work.HiltWorkerFactory +import androidx.work.Configuration import com.mhss.app.mybrain.R import com.mhss.app.mybrain.util.Constants import dagger.hilt.android.HiltAndroidApp +import javax.inject.Inject val Context.dataStore: DataStore by preferencesDataStore(name = Constants.SETTINGS_PREFERENCES) @HiltAndroidApp -class MyBrainApplication : Application() { +class MyBrainApplication : Application(), Configuration.Provider { companion object { lateinit var appContext: Context } + @Inject + lateinit var workerFactory: HiltWorkerFactory + + override fun getWorkManagerConfiguration() = + Configuration.Builder() + .setWorkerFactory(workerFactory) + .build() + override fun onCreate() { super.onCreate() appContext = this diff --git a/app/src/main/java/com/mhss/app/mybrain/data/backup/ExportWorker.kt b/app/src/main/java/com/mhss/app/mybrain/data/backup/ExportWorker.kt new file mode 100644 index 00000000..8736102d --- /dev/null +++ b/app/src/main/java/com/mhss/app/mybrain/data/backup/ExportWorker.kt @@ -0,0 +1,123 @@ +package com.mhss.app.mybrain.data.backup + +import android.content.ContentUris +import android.content.ContentValues +import android.content.Context +import android.net.Uri +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import com.mhss.app.mybrain.data.local.dao.BookmarkDao +import com.mhss.app.mybrain.data.local.dao.DiaryDao +import com.mhss.app.mybrain.data.local.dao.NoteDao +import com.mhss.app.mybrain.data.local.dao.TaskDao +import com.mhss.app.mybrain.domain.model.NotesBackUp +import com.mhss.app.mybrain.util.BackupUtil.toJson +import com.mhss.app.mybrain.util.Constants +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileOutputStream + + +@HiltWorker +class ExportWorker @AssistedInject constructor( + @Assisted appContext: Context, + @Assisted workerParams: WorkerParameters, + private val notesDao: NoteDao, + private val tasksDao: TaskDao, + private val diaryDao: DiaryDao, + private val bookmarksDao: BookmarkDao +) : CoroutineWorker(appContext, workerParams) { + + override suspend fun doWork() = withContext(Dispatchers.IO) { + export() + } + + private suspend fun export(): Result { + return try { + val notes = notesDao.getAllNotes().first() + val folders = notesDao.getAllNoteFolders().first() + val notesBackup = NotesBackUp(notes, folders).toJson() + saveFile(Constants.BACKUP_NOTES_FILE_NAME, notesBackup) + setProgress(workDataOf("progress" to 25)) + + val tasks = tasksDao.getAllTasks().first().toJson() + saveFile(Constants.BACKUP_TASKS_FILE_NAME, tasks) + setProgress(workDataOf("progress" to 50)) + + val diary = diaryDao.getAllEntries().first().toJson() + saveFile(Constants.BACKUP_DIARY_FILE_NAME, diary) + setProgress(workDataOf("progress" to 75)) + + val bookmarks = bookmarksDao.getAllBookmarks().first().toJson() + saveFile(Constants.BACKUP_BOOKMARKS_FILE_NAME, bookmarks) + setProgress(workDataOf("progress" to 100)) + + Result.success(workDataOf("success" to true)) + } catch (e: Exception) { + e.printStackTrace() + Result.failure() + } + } + + + private fun saveFile(fileName: String, content: String) { + val contentResolver = applicationContext.contentResolver + val values = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, fileName) + put(MediaStore.MediaColumns.MIME_TYPE,"text/plain") + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS + File.separator + "${Constants.EXPORT_DIR}/") + } + val fileUri = getFileUri(fileName) ?: contentResolver.insert(MediaStore.Files.getContentUri("external"), values) + + fileUri?.let { + contentResolver.openFileDescriptor(it, "w")?.use { pfd -> + FileOutputStream(pfd.fileDescriptor).use { outputStream -> + outputStream.write(content.toByteArray()) + } + } + } + } + + private fun getFileUri(name: String): Uri? { + val projection = arrayOf( + MediaStore.Files.FileColumns._ID, + MediaStore.Files.FileColumns.DISPLAY_NAME + ) + val selection = "${MediaStore.MediaColumns.RELATIVE_PATH} = ? AND ${MediaStore.Files.FileColumns.DISPLAY_NAME} = ?" + val selectionArgs = arrayOf( + Environment.DIRECTORY_DOCUMENTS + File.separator + "${Constants.EXPORT_DIR}/", + name + ) + val resolver = applicationContext.contentResolver + val query = resolver.query( + MediaStore.Files.getContentUri("external"), + projection, + selection, + selectionArgs, + null + ) + query?.use { cursor -> + val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID) + if (cursor.moveToFirst()) { + val id = cursor.getLong(idColumn) + return ContentUris.withAppendedId( + MediaStore.Files.getContentUri("external"), + id + ) + } + } + return null + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/data/backup/ImportWorker.kt b/app/src/main/java/com/mhss/app/mybrain/data/backup/ImportWorker.kt new file mode 100644 index 00000000..f52778a0 --- /dev/null +++ b/app/src/main/java/com/mhss/app/mybrain/data/backup/ImportWorker.kt @@ -0,0 +1,119 @@ +package com.mhss.app.mybrain.data.backup + +import android.content.ContentResolver +import android.content.Context +import android.net.Uri +import android.provider.OpenableColumns +import androidx.hilt.work.HiltWorker +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import androidx.work.workDataOf +import com.mhss.app.mybrain.R +import com.mhss.app.mybrain.app.getString +import com.mhss.app.mybrain.data.local.dao.BookmarkDao +import com.mhss.app.mybrain.data.local.dao.DiaryDao +import com.mhss.app.mybrain.data.local.dao.NoteDao +import com.mhss.app.mybrain.data.local.dao.TaskDao +import com.mhss.app.mybrain.domain.model.Bookmark +import com.mhss.app.mybrain.domain.model.DiaryEntry +import com.mhss.app.mybrain.domain.model.NotesBackUp +import com.mhss.app.mybrain.domain.model.Task +import com.mhss.app.mybrain.util.BackupUtil.listFromJson +import com.mhss.app.mybrain.util.BackupUtil.objectFromJson +import com.mhss.app.mybrain.util.Constants +import dagger.assisted.Assisted +import dagger.assisted.AssistedInject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.FileInputStream + +@HiltWorker +class ImportWorker @AssistedInject constructor( + @Assisted appContext: Context, + @Assisted workerParams: WorkerParameters, + private val notesDao: NoteDao, + private val tasksDao: TaskDao, + private val diaryDao: DiaryDao, + private val bookmarksDao: BookmarkDao +) : CoroutineWorker(appContext, workerParams) { + + override suspend fun doWork() = withContext(Dispatchers.IO) { + + val backupDir = inputData.getString("uri") ?: return@withContext Result.failure() + + import(backupDir) + + } + + private suspend fun import(uriString: String?): Result { + val contentResolver = applicationContext.contentResolver + + val uri = Uri.parse(uriString) + val fileName = uri.fileName() + + uri?.let { + val json = contentResolver.readTextFromFile(uri) + json?.let { + try { + return when(fileName){ + Constants.BACKUP_NOTES_FILE_NAME -> { + val notesObject = json.objectFromJson() ?: return Result.failure() + val folders = notesObject.folders.map{it.copy(id = 0)} + val notes = notesObject.notes.map{it.copy(id = 0, folderId = null)} + notesDao.insertNoteFolders(folders) + notesDao.insertNotes(notes) + Result.success(workDataOf("success" to getString(R.string.notes))) + } + Constants.BACKUP_TASKS_FILE_NAME -> { + val tasks = json.listFromJson()?.map{it.copy(id = 0)} ?: return Result.failure() + tasksDao.insertTasks(tasks) + Result.success(workDataOf("success" to getString(R.string.tasks))) + } + Constants.BACKUP_DIARY_FILE_NAME -> { + val diaryEntries = json.listFromJson()?.map{it.copy(id = 0)} ?: return Result.failure() + diaryDao.insertEntries(diaryEntries) + Result.success(workDataOf("success" to getString(R.string.diary))) + } + Constants.BACKUP_BOOKMARKS_FILE_NAME -> { + val bookmarks = json.listFromJson()?.map{it.copy(id = 0)} ?: return Result.failure() + bookmarksDao.insertBookmarks(bookmarks) + Result.success(workDataOf("success" to getString(R.string.bookmarks))) + } + else -> Result.failure() + } + }catch (e: Exception){ + return Result.failure() + } + } + } ?: return Result.failure() + } + + private fun ContentResolver.readTextFromFile(uri: Uri?): String? { + return uri?.let { + openFileDescriptor(uri, "r")?.use { pfd -> + FileInputStream(pfd.fileDescriptor).use { inputStream -> + inputStream.bufferedReader().use { it.readText() } + } + } + } + } + + private fun Uri.fileName(): String { + val resolver = applicationContext.contentResolver + val query = resolver.query( + this, + null, + null, + null, + null + ) + query?.use { cursor -> + val id = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) + while (cursor.moveToNext()) { + return cursor.getString(id) + } + } + return "" + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/data/local/dao/BookmarkDao.kt b/app/src/main/java/com/mhss/app/mybrain/data/local/dao/BookmarkDao.kt index ca6e8bb3..98d2aa43 100644 --- a/app/src/main/java/com/mhss/app/mybrain/data/local/dao/BookmarkDao.kt +++ b/app/src/main/java/com/mhss/app/mybrain/data/local/dao/BookmarkDao.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.flow.Flow interface BookmarkDao { @Query("SELECT * FROM bookmarks") - fun getAll(): Flow> + fun getAllBookmarks(): Flow> @Query("SELECT * FROM bookmarks WHERE id = :id") suspend fun getBookmark(id: Int): Bookmark @@ -25,4 +25,7 @@ interface BookmarkDao { @Delete suspend fun deleteBookmark(bookmark: Bookmark) + @Insert + suspend fun insertBookmarks(bookmarks: List) + } \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/data/local/dao/DiaryDao.kt b/app/src/main/java/com/mhss/app/mybrain/data/local/dao/DiaryDao.kt index ac79fb77..29780490 100644 --- a/app/src/main/java/com/mhss/app/mybrain/data/local/dao/DiaryDao.kt +++ b/app/src/main/java/com/mhss/app/mybrain/data/local/dao/DiaryDao.kt @@ -19,6 +19,9 @@ interface DiaryDao { @Insert suspend fun insertEntry(diary: DiaryEntry) + @Insert + suspend fun insertEntries(diary: List) + @Update suspend fun updateEntry(diary: DiaryEntry) diff --git a/app/src/main/java/com/mhss/app/mybrain/data/local/dao/NoteDao.kt b/app/src/main/java/com/mhss/app/mybrain/data/local/dao/NoteDao.kt index f8124a09..23432216 100644 --- a/app/src/main/java/com/mhss/app/mybrain/data/local/dao/NoteDao.kt +++ b/app/src/main/java/com/mhss/app/mybrain/data/local/dao/NoteDao.kt @@ -23,6 +23,9 @@ interface NoteDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertNote(note: Note) + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertNotes(notes: List) + @Update suspend fun updateNote(note: Note) @@ -32,6 +35,9 @@ interface NoteDao { @Insert(onConflict = OnConflictStrategy.IGNORE) suspend fun insertNoteFolder(folder: NoteFolder) + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertNoteFolders(folders: List) + @Update suspend fun updateNoteFolder(folder: NoteFolder) diff --git a/app/src/main/java/com/mhss/app/mybrain/data/local/dao/TaskDao.kt b/app/src/main/java/com/mhss/app/mybrain/data/local/dao/TaskDao.kt index bb878032..63237315 100644 --- a/app/src/main/java/com/mhss/app/mybrain/data/local/dao/TaskDao.kt +++ b/app/src/main/java/com/mhss/app/mybrain/data/local/dao/TaskDao.kt @@ -19,6 +19,9 @@ interface TaskDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertTask(task: Task): Long + @Insert(onConflict = OnConflictStrategy.IGNORE) + suspend fun insertTasks(tasks: List) + @Update suspend fun updateTask(task: Task) diff --git a/app/src/main/java/com/mhss/app/mybrain/data/repository/BookmarkRepositoryImpl.kt b/app/src/main/java/com/mhss/app/mybrain/data/repository/BookmarkRepositoryImpl.kt index 87b4ccab..d8b390a5 100644 --- a/app/src/main/java/com/mhss/app/mybrain/data/repository/BookmarkRepositoryImpl.kt +++ b/app/src/main/java/com/mhss/app/mybrain/data/repository/BookmarkRepositoryImpl.kt @@ -14,7 +14,7 @@ class BookmarkRepositoryImpl( ) : BookmarkRepository { override fun getAllBookmarks(): Flow> { - return bookmarkDao.getAll() + return bookmarkDao.getAllBookmarks() } override suspend fun getBookmark(id: Int): Bookmark { diff --git a/app/src/main/java/com/mhss/app/mybrain/data/repository/CalendarRepositoryImpl.kt b/app/src/main/java/com/mhss/app/mybrain/data/repository/CalendarRepositoryImpl.kt index 299b9536..958bff24 100644 --- a/app/src/main/java/com/mhss/app/mybrain/data/repository/CalendarRepositoryImpl.kt +++ b/app/src/main/java/com/mhss/app/mybrain/data/repository/CalendarRepositoryImpl.kt @@ -26,24 +26,39 @@ class CalendarRepositoryImpl(private val context: Context) : CalendarRepository CalendarContract.Events.DTEND, CalendarContract.Events.EVENT_LOCATION, CalendarContract.Events.ALL_DAY, - CalendarContract.Events.CALENDAR_COLOR, + CalendarContract.Events.EVENT_COLOR, CalendarContract.Events.CALENDAR_ID, CalendarContract.Events.RRULE, CalendarContract.Events.DURATION, + CalendarContract.Events.CALENDAR_COLOR ) + val instancesProjection = arrayOf( + CalendarContract.Instances.EVENT_ID, + CalendarContract.Instances.TITLE, + CalendarContract.Instances.DESCRIPTION, + CalendarContract.Instances.BEGIN, + CalendarContract.Instances.END, + CalendarContract.Instances.EVENT_LOCATION, + CalendarContract.Instances.ALL_DAY, + CalendarContract.Instances.EVENT_COLOR, + CalendarContract.Instances.CALENDAR_ID, + CalendarContract.Instances.RRULE, + CalendarContract.Instances.DURATION, + CalendarContract.Instances.CALENDAR_COLOR + ) + val contentResolver = context.contentResolver + val events = mutableListOf() val uri: Uri = CalendarContract.Events.CONTENT_URI - val contentResolver = context.contentResolver val cur: Cursor? = contentResolver.query( uri, projection, "${CalendarContract.Events.DTSTART} > ? AND ${CalendarContract.Events.DELETED} = 0", // events today or in the future only arrayOf(System.currentTimeMillis().toString()), - "${CalendarContract.Events.DTSTART} ASC" + null ) - val events: MutableList = mutableListOf() if (cur != null) { while (cur.moveToNext()) { val eventId: Long = cur.getLong(ID_INDEX) @@ -55,11 +70,55 @@ class CalendarRepositoryImpl(private val context: Context) : CalendarRepository val location: String? = cur.getString(LOCATION_INDEX) val allDay: Boolean = cur.getInt(ALL_DAY_INDEX) == 1 val color: Int = cur.getInt(COlOR_INDEX) + val calendarColor: Int = cur.getInt(CALENDAR_COLOR_INDEX) val calendarId: Long = cur.getLong(EVENT_CALENDAR_ID_INDEX) val rrule: String = cur.getString(EVENT_RRULE_INDEX) ?: "" val recurring: Boolean = rrule.isNotBlank() - val frequency: String = rrule.extractFrequency() +// val frequency: String = rrule.extractFrequency() + + if (!recurring) + events.add(CalendarEvent( + id = eventId , + title = title, + description = description, + start = start, + end = end, + location = location, + allDay = allDay, + color = if (color != 0) color else calendarColor, + calendarId = calendarId, + )) + } + cur.close() + } + // get recurring events + val startM = java.util.Calendar.getInstance().timeInMillis + val endM = startM + 6 * 30 * 24 * 60 * 60 * 1000L + val builder = CalendarContract.Instances.CONTENT_URI.buildUpon() + ContentUris.appendId(builder, startM) + ContentUris.appendId(builder, endM) + val curI = contentResolver.query( + builder.build(), + instancesProjection, + null, null, null + ) + if (curI != null){ + while (curI.moveToNext()){ + val eventId: Long = curI.getLong(ID_INDEX) + val title: String = curI.getString(TITLE_INDEX) ?: continue + val description: String? = curI.getString(DESC_INDEX) + val start: Long = curI.getLong(START_INDEX) + val duration: String = curI.getString(EVENT_DURATION_INDEX) ?: "" + val end: Long = if (duration.isNotBlank()) duration.extractEndFromDuration(start) else curI.getLong(END_INDEX) + val location: String? = curI.getString(LOCATION_INDEX) + val allDay: Boolean = curI.getInt(ALL_DAY_INDEX) == 1 + val color: Int = curI.getInt(COlOR_INDEX) + val calendarColor: Int = curI.getInt(CALENDAR_COLOR_INDEX) + val calendarId: Long = curI.getLong(EVENT_CALENDAR_ID_INDEX) + val rrule: String = curI.getString(EVENT_RRULE_INDEX) ?: "" + val recurring: Boolean = rrule.isNotBlank() + val frequency: String = rrule.extractFrequency() events.add(CalendarEvent( id = eventId, title = title, @@ -68,16 +127,15 @@ class CalendarRepositoryImpl(private val context: Context) : CalendarRepository end = end, location = location, allDay = allDay, - color = color, + color = if (color != 0) color else calendarColor, calendarId = calendarId, frequency = frequency, recurring = recurring, )) } - cur.close() - events - }else - emptyList() + curI.close() + } + events.sortedBy { it.start } } } @@ -191,6 +249,7 @@ class CalendarRepositoryImpl(private val context: Context) : CalendarRepository private const val EVENT_CALENDAR_ID_INDEX = 8 private const val EVENT_RRULE_INDEX = 9 private const val EVENT_DURATION_INDEX = 10 + private const val CALENDAR_COLOR_INDEX = 11 private const val CALENDAR_ID_INDEX: Int = 0 private const val CALENDAR_NAME_INDEX: Int = 1 diff --git a/app/src/main/java/com/mhss/app/mybrain/domain/model/NotesBackUp.kt b/app/src/main/java/com/mhss/app/mybrain/domain/model/NotesBackUp.kt new file mode 100644 index 00000000..49791e1a --- /dev/null +++ b/app/src/main/java/com/mhss/app/mybrain/domain/model/NotesBackUp.kt @@ -0,0 +1,6 @@ +package com.mhss.app.mybrain.domain.model + +data class NotesBackUp( + val notes: List, + val folders: List +) diff --git a/app/src/main/java/com/mhss/app/mybrain/domain/use_case/calendar/GetAllEventsUseCase.kt b/app/src/main/java/com/mhss/app/mybrain/domain/use_case/calendar/GetAllEventsUseCase.kt index f1c22ad7..994def6a 100644 --- a/app/src/main/java/com/mhss/app/mybrain/domain/use_case/calendar/GetAllEventsUseCase.kt +++ b/app/src/main/java/com/mhss/app/mybrain/domain/use_case/calendar/GetAllEventsUseCase.kt @@ -2,7 +2,7 @@ package com.mhss.app.mybrain.domain.use_case.calendar import com.mhss.app.mybrain.domain.model.CalendarEvent import com.mhss.app.mybrain.domain.repository.CalendarRepository -import com.mhss.app.mybrain.util.date.formatDay +import com.mhss.app.mybrain.util.date.formatDateForMapping import javax.inject.Inject class GetAllEventsUseCase @Inject constructor( @@ -12,7 +12,7 @@ class GetAllEventsUseCase @Inject constructor( return calendarRepository.getEvents() .filter { it.calendarId.toInt() !in excluded } .groupBy { event -> - event.start.formatDay() + event.start.formatDateForMapping() } } } \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarEventWidgetItem.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarEventWidgetItem.kt index 973a1c88..b9a0f711 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarEventWidgetItem.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarEventWidgetItem.kt @@ -30,13 +30,6 @@ fun CalendarEventWidgetItem( Box( GlanceModifier .padding(vertical = 4.dp) - .clickable( - onClick = actionRunCallback( - parameters = actionParametersOf( - eventJson to Gson().toJson(event, CalendarEvent::class.java) - ) - ) - ) ) { Box( modifier = GlanceModifier @@ -81,6 +74,13 @@ fun CalendarEventWidgetItem( ) } } + Box(GlanceModifier.fillMaxSize().clickable( + actionRunCallback( + parameters = actionParametersOf( + eventJson to Gson().toJson(event, CalendarEvent::class.java) + ) + ) + )) {} } } } \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarHomeScreenWidget.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarHomeScreenWidget.kt index 2cb144ac..16452ecd 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarHomeScreenWidget.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarHomeScreenWidget.kt @@ -36,7 +36,6 @@ fun CalendarHomeScreenWidget( ) { Column( modifier = GlanceModifier - .clickable(onClick = actionRunCallback()) .padding(8.dp) ) { Row( @@ -51,30 +50,33 @@ fun CalendarHomeScreenWidget( fontWeight = FontWeight.Bold, fontSize = 16.sp ), - modifier = GlanceModifier.padding(horizontal = 8.dp), + modifier = GlanceModifier + .padding(horizontal = 8.dp) + .clickable(onClick = actionRunCallback()) + , ) Row( - modifier = GlanceModifier.fillMaxWidth().padding(horizontal = 8.dp), + modifier = GlanceModifier + .clickable(onClick = actionRunCallback()) + .fillMaxWidth() + .padding(horizontal = 8.dp), horizontalAlignment = Alignment.End ) { - Button( - text = "", + Image( modifier = GlanceModifier .size(22.dp) - .background(ImageProvider(R.drawable.ic_refresh)) - .padding(8.dp) - , - onClick = actionRunCallback() + .clickable(actionRunCallback()), + provider = ImageProvider(R.drawable.ic_refresh), + contentDescription = "refresh" ) Spacer(GlanceModifier.width(12.dp)) - Button( - text = "", + Image( modifier = GlanceModifier .size(22.dp) - .background(ImageProvider(R.drawable.ic_add)) - .padding(8.dp) + .clickable(actionRunCallback()) , - onClick = actionRunCallback() + provider = ImageProvider(R.drawable.ic_add), + contentDescription = "add event", ) } } @@ -109,7 +111,7 @@ fun CalendarHomeScreenWidget( .padding(start = 4.dp, end = 4.dp) ) { Text( - text = day, + text = day.substring(0, day.indexOf(",")), style = TextStyle( color = ColorProvider(Color.White), fontWeight = FontWeight.Normal, diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarScreen.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarScreen.kt index cf88e572..94bff016 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarScreen.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/calendar/CalendarScreen.kt @@ -152,7 +152,7 @@ fun CalendarScreen( verticalArrangement = Arrangement.spacedBy(12.dp), ) { Text( - text = day, + text = day.substring(0, day.indexOf(",")), style = MaterialTheme.typography.h5 ) events.forEach { event -> diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/glance_widgets/CalendarWidgetActions.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/glance_widgets/CalendarWidgetActions.kt index b8386281..7fe970c1 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/glance_widgets/CalendarWidgetActions.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/glance_widgets/CalendarWidgetActions.kt @@ -12,7 +12,7 @@ import com.mhss.app.mybrain.presentation.main.MainActivity import com.mhss.app.mybrain.util.Constants class AddEventAction : ActionCallback { - override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { val intent = Intent( Intent.ACTION_VIEW, "${Constants.CALENDAR_DETAILS_SCREEN_URI}/ ".toUri(), @@ -24,7 +24,7 @@ class AddEventAction : ActionCallback { } class NavigateToCalendarAction : ActionCallback { - override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { val intent = Intent( Intent.ACTION_VIEW, Constants.CALENDAR_SCREEN_URI.toUri(), @@ -36,7 +36,7 @@ class NavigateToCalendarAction : ActionCallback { } class CalendarWidgetItemClick : ActionCallback { - override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { parameters[eventJson]?.let { val intent = Intent( Intent.ACTION_VIEW, @@ -50,7 +50,7 @@ class CalendarWidgetItemClick : ActionCallback { } class GoToSettingsAction : ActionCallback { - override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) intent.data = Uri.fromParts("package", context.packageName, null) context.startActivity(intent) @@ -58,7 +58,7 @@ class GoToSettingsAction : ActionCallback { } class RefreshCalendarAction : ActionCallback { - override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { val updateIntent = Intent(context, RefreshCalendarWidgetReceiver::class.java) context.sendBroadcast(updateIntent) } diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/glance_widgets/TasksWidgetActions.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/glance_widgets/TasksWidgetActions.kt index 003a8d76..421aa15d 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/glance_widgets/TasksWidgetActions.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/glance_widgets/TasksWidgetActions.kt @@ -10,7 +10,7 @@ import com.mhss.app.mybrain.presentation.main.MainActivity import com.mhss.app.mybrain.util.Constants class AddTaskAction : ActionCallback { - override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { val intent = Intent( Intent.ACTION_VIEW, "${Constants.TASKS_SCREEN_URI}/true".toUri(), @@ -22,7 +22,7 @@ class AddTaskAction : ActionCallback { } class NavigateToTasksAction : ActionCallback { - override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { val intent = Intent( Intent.ACTION_VIEW, "${Constants.TASKS_SCREEN_URI}/false".toUri(), @@ -34,7 +34,7 @@ class NavigateToTasksAction : ActionCallback { } class TaskWidgetItemClickAction : ActionCallback { - override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { parameters[taskId]?.let { val intent = Intent( Intent.ACTION_VIEW, @@ -48,7 +48,7 @@ class TaskWidgetItemClickAction : ActionCallback { } class CompleteTaskAction : ActionCallback { - override suspend fun onRun(context: Context, glanceId: GlanceId, parameters: ActionParameters) { + override suspend fun onAction(context: Context, glanceId: GlanceId, parameters: ActionParameters) { parameters[taskId]?.let { id -> parameters[completed].let { completed -> val intent = Intent(context, CompleteTaskWidgetReceiver::class.java) diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainActivity.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainActivity.kt index bec55c68..f194cb09 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainActivity.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainActivity.kt @@ -1,6 +1,7 @@ package com.mhss.app.mybrain.presentation.main import android.os.Bundle +import android.view.WindowManager.LayoutParams import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels @@ -31,6 +32,7 @@ import com.mhss.app.mybrain.presentation.diary.DiarySearchScreen import com.mhss.app.mybrain.presentation.notes.NoteDetailsScreen import com.mhss.app.mybrain.presentation.notes.NotesScreen import com.mhss.app.mybrain.presentation.notes.NotesSearchScreen +import com.mhss.app.mybrain.presentation.settings.ImportExportScreen import com.mhss.app.mybrain.presentation.tasks.TaskDetailScreen import com.mhss.app.mybrain.presentation.tasks.TasksScreen import com.mhss.app.mybrain.presentation.tasks.TasksSearchScreen @@ -58,6 +60,7 @@ class MainActivity : ComponentActivity() { setContent { val themeMode = viewModel.themeMode.collectAsState(initial = ThemeSettings.AUTO.value) val font = viewModel.font.collectAsState(initial = Rubik.toInt()) + val blockScreenshots = viewModel.blockScreenshots.collectAsState(initial = false) var startUpScreenSettings by remember { mutableStateOf(StartUpScreenSettings.SPACES.value) } val systemUiController = rememberSystemUiController() LaunchedEffect(true) { @@ -65,6 +68,15 @@ class MainActivity : ComponentActivity() { startUpScreenSettings = viewModel.defaultStartUpScreen.first() } } + LaunchedEffect(blockScreenshots.value) { + if (blockScreenshots.value) { + window.setFlags( + LayoutParams.FLAG_SECURE, + LayoutParams.FLAG_SECURE + ) + } else + window.clearFlags(LayoutParams.FLAG_SECURE) + } val startUpScreen = if (startUpScreenSettings == StartUpScreenSettings.SPACES.value) Screen.SpacesScreen.route else Screen.DashboardScreen.route @@ -234,6 +246,9 @@ class MainActivity : ComponentActivity() { it.arguments?.getInt(Constants.FOLDER_ID) ?: -1 ) } + composable(Screen.ImportExportScreen.route) { + ImportExportScreen() + } } } } diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainViewModel.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainViewModel.kt index 8d74b090..86521f73 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainViewModel.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/main/MainViewModel.kt @@ -43,6 +43,7 @@ class MainViewModel @Inject constructor( val themeMode = getSettings(intPreferencesKey(Constants.SETTINGS_THEME_KEY), ThemeSettings.AUTO.value) val defaultStartUpScreen = getSettings(intPreferencesKey(Constants.DEFAULT_START_UP_SCREEN_KEY), StartUpScreenSettings.SPACES.value) val font = getSettings(intPreferencesKey(Constants.APP_FONT_KEY), Rubik.toInt()) + val blockScreenshots = getSettings(booleanPreferencesKey(Constants.BLOCK_SCREENSHOTS_KEY), false) fun onDashboardEvent(event: DashboardEvent) { when(event) { diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/main/SettingsScreen.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/main/SettingsScreen.kt index 246587a4..6905ff4f 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/main/SettingsScreen.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/main/SettingsScreen.kt @@ -12,21 +12,24 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.datastore.preferences.core.booleanPreferencesKey import androidx.datastore.preferences.core.intPreferencesKey import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController import com.mhss.app.mybrain.BuildConfig import com.mhss.app.mybrain.R import com.mhss.app.mybrain.presentation.settings.SettingsBasicLinkItem import com.mhss.app.mybrain.presentation.settings.SettingsItemCard import com.mhss.app.mybrain.presentation.settings.SettingsViewModel +import com.mhss.app.mybrain.presentation.util.Screen import com.mhss.app.mybrain.ui.theme.Rubik import com.mhss.app.mybrain.util.Constants import com.mhss.app.mybrain.util.settings.* @Composable fun SettingsScreen( + navController: NavHostController, viewModel: SettingsViewModel = hiltViewModel() ) { Scaffold( @@ -109,6 +112,41 @@ fun SettingsScreen( ) } } + item { + val block = viewModel + .getSettings( + booleanPreferencesKey(Constants.BLOCK_SCREENSHOTS_KEY), + false + ).collectAsState( + initial = false + ) + BlockScreenshotsSettingsItem( + block.value + ){ + viewModel.saveSettings( + booleanPreferencesKey(Constants.BLOCK_SCREENSHOTS_KEY), + it + ) + } + } + + item { + SettingsItemCard( + cornerRadius = 16.dp, + onClick = { + navController.navigate(Screen.ImportExportScreen.route) + } + ) { + Row { + Icon(painter = painterResource(id = R.drawable.ic_import_export), contentDescription = null) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(R.string.import_data), + style = MaterialTheme.typography.h6 + ) + } + } + } item { Text( @@ -324,14 +362,24 @@ fun AppFontSettingsItem( } } -@Preview -@Composable -fun ThemeItemPreview() { - ThemeSettingsItem() -} - -@Preview @Composable -fun StartUpItemPreview() { - StartUpScreenSettingsItem(0) +fun BlockScreenshotsSettingsItem( + block: Boolean, + onBlockClick: (Boolean) -> Unit = {} +) { + SettingsItemCard( + cornerRadius = 16.dp, + onClick = { + onBlockClick(!block) + }, + vPadding = 10.dp + ) { + Text( + text = stringResource(R.string.block_screenshots), + style = MaterialTheme.typography.h6 + ) + Switch(checked = block, onCheckedChange = { + onBlockClick(it) + }) + } } \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/main/components/MainBottomBar.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/main/components/MainBottomBar.kt index cbc443f5..74eb48d6 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/main/components/MainBottomBar.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/main/components/MainBottomBar.kt @@ -28,9 +28,6 @@ fun MainBottomBar( , contentDescription = stringResource(it.title), ) }, - label = { - Text(stringResource(it.title), style = MaterialTheme.typography.body2) - }, selected = currentDestination?.route == it.route, onClick = { navController.navigate(it.route) { diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/main/components/NavigationGraph.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/main/components/NavigationGraph.kt index b0f9fc34..4e1500b1 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/main/components/NavigationGraph.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/main/components/NavigationGraph.kt @@ -24,7 +24,7 @@ fun NavigationGraph( SpacesScreen(mainNavController) } composable(Screen.SettingsScreen.route){ - SettingsScreen() + SettingsScreen(mainNavController) } } } \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NoteItem.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NoteItem.kt index 1fc7ec62..5ba1c6c4 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NoteItem.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NoteItem.kt @@ -30,7 +30,7 @@ fun NoteItem( Card( modifier = modifier, shape = RoundedCornerShape(20.dp), - elevation = 12.dp + elevation = 4.dp ) { Column( modifier = Modifier @@ -61,7 +61,7 @@ fun NoteItem( Spacer(Modifier.height(8.dp)) MarkdownText( markdown = note.content, - maxLines = 8, + maxLines = 14, onClick = {onClick(note)}, fontSize = 12.sp ) diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NotesScreen.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NotesScreen.kt index 299cd305..83c58177 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NotesScreen.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NotesScreen.kt @@ -11,6 +11,9 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* @@ -183,16 +186,10 @@ fun NotesScreen( } } } else { - LazyVerticalGrid( - columns = GridCells.Adaptive(150.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Adaptive(150.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), - contentPadding = PaddingValues( - top = 12.dp, - bottom = 24.dp, - start = 12.dp, - end = 12.dp - ) + contentPadding = PaddingValues(12.dp) ) { items(uiState.notes) { note -> key(note.id) { @@ -209,7 +206,7 @@ fun NotesScreen( ) ) }, - modifier = Modifier.animateItemPlacement().height(220.dp) + modifier = Modifier.padding(bottom = 12.dp) ) } } diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NotesSearchScreen.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NotesSearchScreen.kt index 086fad18..4ddcb3ab 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NotesSearchScreen.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/notes/NotesSearchScreen.kt @@ -2,11 +2,11 @@ package com.mhss.app.mybrain.presentation.notes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid -import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.foundation.lazy.staggeredgrid.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.OutlinedTextField import androidx.compose.material.Text @@ -73,9 +73,8 @@ fun NotesSearchScreen( } } } else { - LazyVerticalGrid( - columns = GridCells.Adaptive(150.dp), - verticalArrangement = Arrangement.spacedBy(12.dp), + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Adaptive(150.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), contentPadding = PaddingValues(12.dp) ){ @@ -94,7 +93,7 @@ fun NotesSearchScreen( ) ) }, - modifier = Modifier.animateItemPlacement().height(220.dp) + modifier = Modifier.padding(bottom = 12.dp) ) } } diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/settings/ImportExportScreen.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/settings/ImportExportScreen.kt new file mode 100644 index 00000000..c2ab51ee --- /dev/null +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/settings/ImportExportScreen.kt @@ -0,0 +1,217 @@ +package com.mhss.app.mybrain.presentation.settings + +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.work.* +import com.google.accompanist.permissions.ExperimentalPermissionsApi +import com.google.accompanist.permissions.rememberPermissionState +import com.mhss.app.mybrain.R +import com.mhss.app.mybrain.app.MyBrainApplication +import com.mhss.app.mybrain.data.backup.ExportWorker +import com.mhss.app.mybrain.data.backup.ImportWorker +import java.util.UUID + +@OptIn(ExperimentalPermissionsApi::class) +@Composable +fun ImportExportScreen() { + + val writeStoragePermission = rememberPermissionState( + android.Manifest.permission.WRITE_EXTERNAL_STORAGE + ) + val workManager = remember { + WorkManager.getInstance(MyBrainApplication.appContext) + } + val exportRequest by remember { + derivedStateOf { + OneTimeWorkRequestBuilder().build() + } + } + var importRequestId by remember { + mutableStateOf(null) + } + + val exportWorkInfo = workManager.getWorkInfoByIdLiveData(exportRequest.id).observeAsState() + val importWorkInfo = if (importRequestId != null) { + workManager.getWorkInfoByIdLiveData(importRequestId!!).observeAsState() + } else { + null + } + + val exportProgress = exportWorkInfo.value?.progress?.getInt("progress", 0) + + val chooseDirectoryLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { + it?.let { uri -> + val importRequest = + OneTimeWorkRequestBuilder().setInputData(workDataOf("uri" to uri.toString())) + .build() + importRequestId = importRequest.id + workManager.enqueueUniqueWork("import", ExistingWorkPolicy.KEEP, importRequest) + } + } + + Scaffold( + topBar = { + TopAppBar( + title = { + Text( + text = stringResource(R.string.export_import), + style = MaterialTheme.typography.h5.copy(fontWeight = FontWeight.Bold) + ) + }, + backgroundColor = MaterialTheme.colors.background, + elevation = 0.dp, + ) + } + ) { + Column(Modifier.fillMaxSize()) { + Button( + onClick = { + if (Build.VERSION.SDK_INT < 29 && !writeStoragePermission.hasPermission) { + writeStoragePermission.launchPermissionRequest() + } else if (Build.VERSION.SDK_INT < 29 && !writeStoragePermission.shouldShowRationale) { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + intent.data = Uri.fromParts( + "package", + MyBrainApplication.appContext.packageName, + null + ) + MyBrainApplication.appContext.startActivity(intent) + } else { + workManager.enqueueUniqueWork( + "export", + ExistingWorkPolicy.KEEP, + exportRequest + ) + } + }, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.CenterHorizontally) + .padding(12.dp), + shape = RoundedCornerShape(8.dp) + ) { + if (Build.VERSION.SDK_INT >= 29 || writeStoragePermission.hasPermission) + Icon(painterResource(id = R.drawable.ic_export), null) + Text( + text = stringResource( + if (Build.VERSION.SDK_INT < 29 && !writeStoragePermission.hasPermission) + R.string.grant_permission_to_export + else + R.string.export + ), + style = MaterialTheme.typography.h6.copy(fontWeight = FontWeight.Bold), + modifier = Modifier.padding(12.dp) + ) + } + + if (exportProgress != null && exportProgress > 0) { + LinearProgressIndicator( + progress = exportProgress.toFloat() / 100, + modifier = Modifier + .fillMaxWidth() + .padding(12.dp) + ) + Text( + text = "$exportProgress%", + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.h6.copy(fontWeight = FontWeight.Bold), + textAlign = TextAlign.Center + ) + } + + if (exportWorkInfo.value?.outputData?.getBoolean("success", false) == true) { + Text( + text = stringResource(R.string.export_success), + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.body2.copy(fontWeight = FontWeight.Bold), + textAlign = TextAlign.Center + ) + } else if (exportWorkInfo.value?.state == WorkInfo.State.FAILED) { + Text( + text = stringResource(R.string.export_failed), + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.body2.copy(fontWeight = FontWeight.Bold), + textAlign = TextAlign.Center, + color = MaterialTheme.colors.error + ) + } + + Button( + onClick = { + chooseDirectoryLauncher.launch(arrayOf("text/plain")) + }, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.CenterHorizontally) + .padding(12.dp), + shape = RoundedCornerShape(8.dp) + ) { + Icon(painterResource(id = R.drawable.ic_import), null) + Text( + text = stringResource(R.string.import_data), + style = MaterialTheme.typography.h6.copy(fontWeight = FontWeight.Bold), + modifier = Modifier.padding(12.dp) + ) + } + + if (importWorkInfo?.value?.outputData?.getString("success")?.isNotBlank() == true) + Text( + text = stringResource( + R.string.import_success, + importWorkInfo.value?.outputData?.getString("success") ?: "" + ), + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + style = MaterialTheme.typography.body2.copy(fontWeight = FontWeight.Bold), + textAlign = TextAlign.Start, + ) + + + if (importWorkInfo?.value?.state == WorkInfo.State.FAILED) { + Text( + text = stringResource(R.string.import_failed), + modifier = Modifier + .fillMaxWidth() + .padding(12.dp), + style = MaterialTheme.typography.body2.copy(fontWeight = FontWeight.Bold), + textAlign = TextAlign.Center, + color = MaterialTheme.colors.error + ) + } + if (importWorkInfo?.value?.state == WorkInfo.State.RUNNING){ + CircularProgressIndicator( + Modifier + .align(Alignment.CenterHorizontally) + .padding(12.dp)) + Text(text = stringResource(R.string.importing), + Modifier + .align(Alignment.CenterHorizontally) + .padding(8.dp), + style = MaterialTheme.typography.body2.copy(fontWeight = FontWeight.Bold), + textAlign = TextAlign.Center) + } + + } + } +} diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/settings/SettingsItemCard.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/settings/SettingsItemCard.kt index a0aca6b7..3caf7f81 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/settings/SettingsItemCard.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/settings/SettingsItemCard.kt @@ -14,6 +14,8 @@ import androidx.compose.ui.unit.dp fun SettingsItemCard( modifier: Modifier = Modifier, cornerRadius: Dp = 25.dp, + hPadding: Dp = 12.dp, + vPadding: Dp = 16.dp, onClick: () -> Unit = {}, content: @Composable RowScope.() -> Unit, ) { @@ -28,7 +30,7 @@ fun SettingsItemCard( Modifier .fillMaxWidth() .clickable { onClick() } - .padding(horizontal = 12.dp, vertical = 16.dp), + .padding(horizontal = hPadding, vertical = vPadding), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, content = content diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TaskWidgetItem.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TaskWidgetItem.kt index 8bf160d7..ca6d09c0 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TaskWidgetItem.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TaskWidgetItem.kt @@ -32,22 +32,29 @@ fun TaskWidgetItem( task: Task ) { Box( - GlanceModifier - .padding(bottom = 3.dp) - .clickable( - onClick = actionRunCallback( - parameters = actionParametersOf( - taskId to task.id - ) - ) - ) + GlanceModifier.padding(bottom = 3.dp) ) { Column( GlanceModifier .background(ImageProvider(R.drawable.small_item_rounded_corner_shape)) .padding(10.dp) + .clickable( + actionRunCallback( + parameters = actionParametersOf( + taskId to task.id + ) + ) + ) ) { - Row(GlanceModifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + Row(GlanceModifier + .fillMaxWidth() + .clickable( + actionRunCallback( + parameters = actionParametersOf( + taskId to task.id + ) + )) + , verticalAlignment = Alignment.CenterVertically) { TaskWidgetCheckBox( isComplete = task.isCompleted, task.priority.toPriority().color, @@ -68,6 +75,12 @@ fun TaskWidgetItem( textDecoration = if (task.isCompleted) TextDecoration.LineThrough else TextDecoration.None ), maxLines = 2, + modifier = GlanceModifier.clickable(actionRunCallback( + parameters = actionParametersOf( + taskId to task.id, + completed to !task.isCompleted + ) + )) ) } if (task.dueDate != 0L) { diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TasksHomeScreenWidget.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TasksHomeScreenWidget.kt index fc7dff9a..5038651c 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TasksHomeScreenWidget.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TasksHomeScreenWidget.kt @@ -33,9 +33,7 @@ fun TasksHomeScreenWidget( .cornerRadius(25.dp) ) { Column( - modifier = GlanceModifier - .clickable(onClick = actionRunCallback()) - .padding(8.dp) + modifier = GlanceModifier.padding(8.dp) ) { Row( GlanceModifier.fillMaxWidth(), @@ -49,20 +47,24 @@ fun TasksHomeScreenWidget( fontWeight = FontWeight.Bold, fontSize = 16.sp ), - modifier = GlanceModifier.padding(horizontal = 8.dp), + modifier = GlanceModifier + .padding(horizontal = 8.dp) + .clickable(actionRunCallback()), ) Row( - modifier = GlanceModifier.fillMaxWidth().padding(horizontal = 8.dp), + modifier = GlanceModifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .clickable(actionRunCallback()), horizontalAlignment = Alignment.End ) { - Button( - text = "", + Image( + provider = ImageProvider(R.drawable.ic_add), modifier = GlanceModifier .size(22.dp) - .background(ImageProvider(R.drawable.ic_add)) - .padding(8.dp) + .clickable(actionRunCallback()) , - onClick = actionRunCallback() + contentDescription = "Add task" ) } } diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TasksViewModel.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TasksViewModel.kt index 6dd78143..58b4082a 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TasksViewModel.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/tasks/TasksViewModel.kt @@ -33,6 +33,7 @@ class TasksViewModel @Inject constructor( private val getAllTasks: GetAllTasksUseCase, private val getTaskUseCase: GetTaskByIdUseCase, private val updateTask: UpdateTaskUseCase, + private val completeTask: UpdateTaskCompletedUseCase, getSettings: GetSettingsUseCase, private val saveSettings: SaveSettingsUseCase, private val addAlarm: AddAlarmUseCase, @@ -85,11 +86,7 @@ class TasksViewModel @Inject constructor( tasksUiState = tasksUiState.copy(error = getString(R.string.error_empty_title)) } is TaskEvent.CompleteTask -> viewModelScope.launch { - updateTask( - event.task.copy( - isCompleted = event.complete, - ) - ) + completeTask(event.task.id, event.complete) if (event.complete) deleteAlarm(event.task.id) } diff --git a/app/src/main/java/com/mhss/app/mybrain/presentation/util/Screen.kt b/app/src/main/java/com/mhss/app/mybrain/presentation/util/Screen.kt index 7ec874f0..1b62f3d9 100644 --- a/app/src/main/java/com/mhss/app/mybrain/presentation/util/Screen.kt +++ b/app/src/main/java/com/mhss/app/mybrain/presentation/util/Screen.kt @@ -23,4 +23,5 @@ sealed class Screen(val route: String) { object CalendarScreen : Screen("calendar_screen") object CalendarEventDetailsScreen : Screen("calendar_event_details_screen/{${Constants.CALENDAR_EVENT_ARG}}") object NoteFolderDetailsScreen : Screen("note_folder_details_screen/{${Constants.FOLDER_ID}}") + object ImportExportScreen : Screen("import_export_screen") } \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/util/BackupUtil.kt b/app/src/main/java/com/mhss/app/mybrain/util/BackupUtil.kt new file mode 100644 index 00000000..759cc901 --- /dev/null +++ b/app/src/main/java/com/mhss/app/mybrain/util/BackupUtil.kt @@ -0,0 +1,46 @@ +package com.mhss.app.mybrain.util + +import com.squareup.moshi.* +import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory +import java.util.* + +object BackupUtil { + + inline fun List.toJson(): String{ + val moshi = Moshi.Builder().add(UUIDAdapter()).addLast(KotlinJsonAdapterFactory()).build() + val adapter: JsonAdapter> = moshi.adapter(Types.newParameterizedType(List::class.java, T::class.java)) + return adapter.toJson(this) + } + + inline fun T.toJson(): String{ + val moshi = Moshi.Builder().add(UUIDAdapter()).addLast(KotlinJsonAdapterFactory()).build() + val adapter: JsonAdapter = moshi.adapter(T::class.java) + return adapter.toJson(this) + } + + inline fun String.objectFromJson(): T?{ + val moshi = Moshi.Builder().add(UUIDAdapter()).addLast(KotlinJsonAdapterFactory()).build() + val adapter: JsonAdapter = moshi.adapter(T::class.java) + return adapter.fromJson(this) + } + + inline fun String.listFromJson(): List?{ + val moshi = Moshi.Builder().add(UUIDAdapter()).addLast(KotlinJsonAdapterFactory()).build() + val adapter: JsonAdapter> = moshi.adapter(Types.newParameterizedType(List::class.java, T::class.java)) + return adapter.fromJson(this) + } + + +} + +class UUIDAdapter { + @FromJson + fun fromJson(json: String): UUID { + return UUID.fromString(json) + } + + @ToJson + fun toJson(uuid: UUID): String { + return uuid.toString() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/util/Constants.kt b/app/src/main/java/com/mhss/app/mybrain/util/Constants.kt index 490483b1..3ded569c 100644 --- a/app/src/main/java/com/mhss/app/mybrain/util/Constants.kt +++ b/app/src/main/java/com/mhss/app/mybrain/util/Constants.kt @@ -20,6 +20,7 @@ object Constants { const val DIARY_ORDER_KEY = "diary_order" const val EXCLUDED_CALENDARS_KEY = "excluded_calendars" const val APP_FONT_KEY = "app_font" + const val BLOCK_SCREENSHOTS_KEY = "block_screen_shots" // Navigation const val TASK_ID_ARG = "task_id" @@ -41,4 +42,11 @@ object Constants { const val GITHUB_ISSUES_LINK = "https://github.com/mhss1/ByBrain/issues" const val GITHUB_RELEASES_LINK = "https://github.com/mhss1/ByBrain/releases" + // Backup + const val EXPORT_DIR = "MyBrain Export" + const val BACKUP_NOTES_FILE_NAME = "mybrain_notes.txt" + const val BACKUP_TASKS_FILE_NAME = "mybrain_tasks.txt" + const val BACKUP_DIARY_FILE_NAME = "mybrain_diary.txt" + const val BACKUP_BOOKMARKS_FILE_NAME = "mybrain_bookmarks.txt" + } \ No newline at end of file diff --git a/app/src/main/java/com/mhss/app/mybrain/util/calendar/CalendarUtil.kt b/app/src/main/java/com/mhss/app/mybrain/util/calendar/CalendarUtil.kt index 98d5b2b3..1b80b278 100644 --- a/app/src/main/java/com/mhss/app/mybrain/util/calendar/CalendarUtil.kt +++ b/app/src/main/java/com/mhss/app/mybrain/util/calendar/CalendarUtil.kt @@ -26,8 +26,8 @@ fun CalendarEvent.getEventDuration(): String { fun String.extractEndFromDuration(start: Long): Long { return try { - val duration = this.substring(1, this.length - 1) - start + duration.toLong() + val duration = this.substring(1, this.length - 1).toLong() * 1000 + start + duration }catch (e: Exception) { start } diff --git a/app/src/main/java/com/mhss/app/mybrain/util/date/DateUtils.kt b/app/src/main/java/com/mhss/app/mybrain/util/date/DateUtils.kt index 830c404f..ec633b1d 100644 --- a/app/src/main/java/com/mhss/app/mybrain/util/date/DateUtils.kt +++ b/app/src/main/java/com/mhss/app/mybrain/util/date/DateUtils.kt @@ -21,8 +21,8 @@ fun Long.fullDate(): String { return sdf.format(this) } -fun Long.formatDay(): String { - val sdf = SimpleDateFormat("EEEE d", Locale.getDefault()) +fun Long.formatDateForMapping(): String { + val sdf = SimpleDateFormat("EEEE d, MMM yyy", Locale.getDefault()) return sdf.format(this) } diff --git a/app/src/main/res/drawable/ic_export.xml b/app/src/main/res/drawable/ic_export.xml new file mode 100644 index 00000000..46d5d587 --- /dev/null +++ b/app/src/main/res/drawable/ic_export.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_import.xml b/app/src/main/res/drawable/ic_import.xml new file mode 100644 index 00000000..dcfb7840 --- /dev/null +++ b/app/src/main/res/drawable/ic_import.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_import_export.xml b/app/src/main/res/drawable/ic_import_export.xml new file mode 100644 index 00000000..c40dc3b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_import_export.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index cb6399b8..0bd5ebf7 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -56,8 +56,6 @@ هل أنت متأكد من حذف هذه العلامة المرجعية؟ هل أنت متأكد من حذف هذه من اليوميات؟ إلغاء - الدعم بواسطة : - شكراً على دعمك ❤ ليس لديك أي مهام\n أظغط على زر + لإضافة مهام جديدة \n أو \n بواسطة الإختصار من قائمة الاعدادات السريعة بجهازك ليس لديك أي ملاحظات.\n اضغط على + لإضافة ملاحظة جديدة ليس لديك أي يوميات.\n اضغط على + لإضافة يومية جديدة @@ -151,4 +149,14 @@ هل انت متأكد من حذف الملف و كل محنوياته؟ حفظ تعديل الملف + منع لقطات الشاشة + تصدير\استيراد البيانات + تصدير + تم تصدير البيانات الى \"Documents/MyBrain Export\" + حدث خطأ أثناء التصدير + استيراد البيانات + تم تصدير %1$s + حدث خطأ أثناء الاستيراد. تأكد من عدم نغيير اسم الملف او المحتوى + منح اذن التخزين للتصدير + يتم الاستيراد.... \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 00000000..fbb50aea --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,161 @@ + + My Brain + + Benachrichtigungskanal zum Senden von Aufgabenerinnerungen + Erinnerungen + Fertig + Dashboard + Einstellungen + Übersicht + App Theme + Helles Theme + Dunkles Theme + Auto + Startbildschirm + Über + App Version + Projekt auf GitHub + Datenschutzrichtlinien + Produkt + Notizen + Aufgaben + Tagebuch + Lesezeichen + Kalender + Aufgabe hinzufügen + Aufgabe zu My Brain hinzufügen + Suche + Titel + Unteraugabe löschen + Unteraufgabe hinzufügen + Beschreibung + Fordere ein Feature an / Melde einen Fehler + Niedrig + Mittel + Hoch + Fälligkeitsdatum + Titel darf nicht leer sein + Priorität + Sortieren nach + Alphabetisch + Erstellungsdatum + Änderungsdatum + Aufsteigend + Absteigend + Komplette Aufgabe anzeigen + Aufgabe suchen + Aufgabe speichern + Aufgabe löschen + Aufgabe löschen? + Notiz löschen? + Lesezeichen löschen? + Eintrag löschen? + Diese Aufgabe wirklich löschen?: \n \"%1$s\"" + Diese Notiz wirklich löschen?: \n \"%1$s\"" + Dieses Lesezeichen wirklich löschen?:"" + Diesen Eintrag wirklich löschen?:"" + Abbrechen + Du hast noch keine Aufgaben\n Klicke auf den + Button um eine neue\n Aufgabe oder \nin dem du den Shortcut in den\n Schnelleinstellungen benutzt + Du hast noch keine Notizen\n Klicke auf den + Butten um eine neue Notiz hinzuzufügen + Du hast noch keine Einträge\n Klicke auf den + Button um einen neuen Eintrag hinzuzufügen + Du hast noch keine Lesezeichen\n Klicke auf den + Button um ein neues\n Lesezeichen hinzuzufügen\n oder \nnutze die Teilen funktion deines Browsers + Projekt Fahrplan + Liste + Gitter + Notizinhalt (unterstützt Markdown) + Notiz löschen + Notiz darf nicht leer sein + Notiz anpinnen + Lesemodus + Zeige als + Suche nach Titel oder Inhalt + Füge Notiz hinzu + Lesezeichen Link + Zu Aufgaben hinzufügen + Lesezeichen erfolgreich gespeichert + Ungültige URL + füge Lesezeichen hinzu + Link öffnen + Lesezeichen suchen + Lesezeichen löschen + URL + Änderungen verwerfen + Eintrag hinzufügen + Unglaublich + Gut + Okay + Schlecht + Schrecklich + Eintrag löschen + Inhalt + Eintrag speichern + Tagebuch suchen + Stimmung + %1$s - %2$s at %3$s + %1$s - %2$s + Ganztags + Für dieses Feature werden Leserechte für deinen Kalender benötigt.\nBitte erteile deine Erlaubnis + Für dieses Feature werden Schreibrechte für deinen Kalender benötigt.\nBitte erteile deine Erlaubnis + Erlaubnis erteilen + Gehe zu Einstellungen + Ereignis hinzufügen + anzuzeigende Kalender: + Zu Aufgaben hinzugefügt + Tagebuch Diagramm + Deine Stimmung in diesem Monat + Deine Stimmung in diesem Jahr + %1$d%% + Stimmungs Tendenz + Stimmungs Zusammenfassung + "Deine Stimmung war " + " die meiste Zeit " + In den letzten 30 Tagen + Im letzten Jahr + Keine Daten bis jetzt + Keine Ereignis bis jetzt + Aufgaben Anzahl + Du bist fertig + mit den Aufgaben der letzten Woche + Keine Aufgaben bis jetzt + Lade… + Drücke den Aktualisieren Button wenn du die Erlaubnis bereits erteilt hast + Donnerstag 30 + Sonntag 5 + Abendessen mit Ali + CS Vortrag + Zahnarztbesuch + Lebensmittel kaufen + Mama anrufen + App Schriftart + Systemstandard + Ereignis löschen + Standort + Nicht wiederholen + Jeden Tag + Jede Woche + Jeden Monat + Jedes Jahr + Ereignis sollte nicht in der Vergangenheit liegen + Ereignis löschen? + Dieses Ereignis wirklich löschen? + Ordner + Ordner erstellen + Ordner existiert bereits + Name + Ordner wechseln + Keine + Ordner löschen + Bist du dir sicher das du diesen Ordner und dessen Inhalt löschen möchtest? + Speichern + Ordner bearbeiten + Bildschirmfotos blockieren + Export/Import + Export + Successfully exported data to \"Documents/MyBrain Export\" + Something went wrong while exporting + Import data + Successfully imported %1$s + Something went wrong while importing. make sure that the file name and content are not corrupted + Grant permission to export + Importing.... + \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 383c95e8..4e2affd3 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -52,8 +52,6 @@ क्या आप वाकई इस बुकमार्क को हटाना चाहते हैं क्या आप वाकई इस एंट्री को हटाना चाहते हैं रद्द करे - समर्थन करे: - आपके समर्थन के लिए धन्यवाद ❤ प्रोजेक्ट रोडमैप सूची ग्रिड @@ -150,4 +148,14 @@ क्या आप वाकई इस फ़ोल्डर और इसकी सभी सामग्री को हटाना चाहते हैं? बचाना फोल्डर संपादित करें + Block screenshots + Export/Import + Export + Successfully exported data to \"Documents/MyBrain Export\" + Something went wrong while exporting + Import data + Successfully imported %1$s + Something went wrong while importing. make sure that the file name and content are not corrupted + Grant permission to export + Importing.... \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index ccd44ac3..a5a2307c 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -56,8 +56,6 @@ Czy na pewno chcesz usunąć tą zakładkę"" Czy na pewno chcesz usunąć ten wpis"" Anuluj - Możesz nas wesprzeć : - Dziękujemy za wsparcie ❤ Nie dodano żadnych zadań\nAby dodać nowe Zadanie kliknij przycisk +\n lub \nużyj skrótu na pasku szybkich ustawień Nie dodano żadnych notatek\n Aby dodać nową Notatkę kliknij przycisk + Nie dodano żadnych wpisów\n Aby dodać nowy wpis kliknij przycisk + @@ -151,4 +149,14 @@ Czy Zapisz Edytuj folder + Block screenshots + Export/Import + Export + Successfully exported data to \"Documents/MyBrain Export\" + Something went wrong while exporting + Import data + Successfully imported %1$s + Something went wrong while importing. make sure that the file name and content are not corrupted + Grant permission to export + Importing.... \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 00000000..9585c4bc --- /dev/null +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,161 @@ + + My Brain + + Canal de notificação para enviar lembretes de tarefas + Lembretes + Concluído + Dashboard + Configurações + Spaces + Temas + Tema Claro + Tema Escuro + Automático + Tela de início + Sobre + Versão + Projeto no GitHub + Política de Privacidade + Produto + Notas + Tarefas + Diário + Favoritos + Agenda + Adicionar tarefa + Adicionar tarefa ao My Brain + Procurar + Título + Remover subtarefa + Adicionar subtarefa + Descrição + Solicite um recurso / Reporte um erro + Baixa + Média + Alta + Válido até + O título não pode estar vázio + Prioridade + Ordenar por + Alfabética + Data de criação + Data de modificação + Crescente + Decrescente + Mostrar tarefas concluídas + Procurar tarefas + Salvar Tarefa + Remover Tarefa + Remover Tarefa? + Remover Nota? + Remover Favorito? + Remover Entrada? + Você deseja realmente remover a Tarefa: \n \"%1$s\"" + Você deseja realmente remover a Nota: \n \"%1$s\"" + Você deseja realmente remover este bookmark"" + Você deseja realmente remover esta entrada"" + Cancelar + Você não tem nenhuma tarefa\n Clique no botão + para adicionar uma nova Tarefa\n ou \nuse o atalho no menu das configurações rápidas + Você não tem nenhuma nota\n Clique no botão + para adicionar uma nova Nota + Você não tem nenhuma entrada\n Clique no botão + para adicionar uma nova entrada + Você não tem nenhum favorito\n Clique no botão + para adicionar um novo favorito\n ou \nuse a opção de compartilhar de qualquer navegador + Metas do Projeto + Lista + Grade + Conteúdo da nota (suporta markdown) + Remover Nota + A Nota não pode estar vázia + Fixar nota + Modo de leitura + Ver como + Procurar por título ou conteúdo + Adicionar nota + Salvar link como favorito + Adicionar a tarefas + Favorito salvo com sucesso + URL Inválida + Adicionar favorito + Abrir link + Pesquisar Favoritos + Remover Favoritos + URL + Cancelar mudanças + Adicionar entrada + Ótimo + Bom + Regular + Mau + Péssimo + Remover entrada + Conteúdo + Salvar entrada + Pesquisar diário + Humor + %1$s - %2$s às %3$s + %1$s - %2$s + O dia todo + Permissão de Leitura na Agenda necessária para que este recurso esteja disponível.\nPor favor, conceda a permissão + Permissão de Escrita na Agenda necessária para que este recurso esteja disponível.\nPor favor, conceda a permissão + Conceda a permissão + Ir para configurações + Adicionar evento + Incluir agendas : + Adicionado a tarefas + Gráfico do Diário + Seu humor durante o mês + Seu humor durante o ano + %1$d%% + Humor + Humor + "Seu humor estava " + " na maior parte do tempo " + Últimos 30 dias + Último ano + Sem dados ainda + Sem eventos ainda + Tarefas + Você concluiu + das Tarefas na última semana + Sem tarefas ainda + Carregando… + Se você já concedeu a permissão, clique no botão atualizar + Quinta 30 + Domingo 5 + Jantar com o Ali + Aula de Computação + Consulta dentista + Fazer compras + Ligar para mamãe + Fonte + Padrão do Sistema + Remover evento + Localização + Não repetir + Todo dia + Toda semana + Todo mês + Todo ano + O Evento não deve ser no passado + Remover Evento? + Você tem certeza que deseja remover o evento? + Pastas + Criar pasta + A pasta já existe + Nome + Mudar pasta + Nenhum + Remover pasta + Você tem certeza que deseja remover esta pasta e todo o seu conteúdo? + Salvar + Editar pasta + Block screenshots + Export/Import + Export + Successfully exported data to \"Documents/MyBrain Export\" + Something went wrong while exporting + Import data + Successfully imported %1$s + Something went wrong while importing. make sure that the file name and content are not corrupted + Grant permission to export + Importing.... + diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml new file mode 100644 index 00000000..b10d4c19 --- /dev/null +++ b/app/src/main/res/values-ro/strings.xml @@ -0,0 +1,161 @@ + + My Brain + + Canal de notificare pentru trimiterea de notificări de reamintire a sarcinilor + Mementouri + Complet + Panou + Setări + Spații + Tema aplicației + Temă luminoasă + Temă întunecată + Automat + Ecran de pornire + Despre + Versiunea aplicației + Proiectul pe GitHub + Politica de confidențialitate + Produs + Note + Sarcini + Jurnal + Marcaj + Calendar + Adaugă sarcină + Adaugă sarcina la My Brain + Caută + Titlu + Șterge sub-sarcină + Adaugă sub-sarcină + Descriere + Solicită o funcție / Raportează un bug + Scăzută + Medie + Înaltă + Data limită + Titlul nu poate fi gol + Prioritate + Ordonează după + Alfabetic + Data creării + Data modificării + Crescător + Descrescător + Arată sarcinile completate + Caută sarcini + Salvează sarcina + Șterge sarcina + Ștergeți sarcina? + Ștergeți nota? + Ștergeți marcajul? + Ștergeți intrarea? + Sigur dorești ștergerea sarcinii: \n \"%1$s\""? + Sigur dorești ștergerea notei: \n \"%1$s\""? + Sigur dorești ștergerea marcajului""? + Sigur dorești ștergerea acestei intrări""? + Anulare + Nu aveți nicio sarcină\n Faceți clic pe butonul + pentru a adăuga o nouă sarcină\n sau \nprin utilizarea comenzii rapide din meniul de setări rapide + Nu aveți nicio notă\n Faceți clic pe butonul + pentru a adăuga o nouă notă + Nu aveți nicio intrare\n Faceți clic pe butonul + pentru a adăuga o nouă intrare + Nu aveți niciun marcaj\n Faceți clic pe butonul + pentru a adăuga un nou marcaj\n sau \nprin utilizarea opțiunii de distribuire din orice browser + Harta proiectului + Listă + Grilă + Conținutul notei (suportă markdown) + Șterge nota + Nota nu poate fi goală + Fixează nota + Modul de citire + Vezi ca + Căutare după titlu sau conținut + Adaugă notă + Marchează link-ul + Adaugă la sarcini + Marcaj salvat cu succes + Adresă URL invalidă + Adaugă marcaj + Deschide link + Caută marcaje + Șterge marcajul + Adresă URL + Anulează modificările + Adaugă intrare + Minunat + Bun + Okay + Rău + Teribil + Șterge intrarea + Conținut + Salvează intrarea + Caută în Jurnal + Dispoziție + %1$s - %2$s at %3$s + %1$s - %2$s + Toată ziua + Este necesară permisiunea de citire a calendarului pentru ca această funcție să fie disponibilă.\nTe rog să acorzi permisiunea + Este necesară permisiunea de scriere a calendarului pentru ca această funcție să fie disponibilă.\nTe rog să acorzi permisiunea + Acordă permisiunea + Accesează setările + Adaugă eveniment + Include calendarele: + Adăugat la sarcini + Graficul jurnalului + Dispoziția ta pe parcursul lunii + Dispoziția ta pe parcursul anului + %1$d%% + Fluxul dispoziției + Rezumatul dispoziției + "Dispoziția ta a fost " + " majoritatea timpului " + Ultimele 30 de zile + Anul trecut + Nu există încă date + Niciun eveniment încă + Rezumatul sarcinilor + Ai finalizat + de sarcini în ultima săptămână + Nicio sarcină încă + Încărcare… + Faceți clic pe butonul de reîmprospătare dacă ați acordat deja permisiunea + Joi 30 + Duminică 5 + Cina cu Ali + Curs CS + Vizitează dentistul + Cumpără alimente + Sun-o pe mama + Fontul aplicației + Implicit al sistemului + Șterge evenimentul + Locație + Nu se repetă + În fiecare zi + În fiecare săptămână + În fiecare lună + În fiecare an + Evenimentul nu ar trebui să fie în trecut + Ștergeți evenimentul? + Sigur dorești ștergerea acestui eveniment? + Dosare + Creează un dosar + Dosarul există deja + Nume + Schimbă dosarul + None + Șterge dosarul + Sunteți sigur că doriți să ștergeți acest dosar și tot conținutul său? + Save + Editează dosarul + Block screenshots + Export/Import + Export + Successfully exported data to \"Documents/MyBrain Export\" + Something went wrong while exporting + Import data + Successfully imported %1$s + Something went wrong while importing. make sure that the file name and content are not corrupted + Grant permission to export + Importing.... + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a76bc5f7..b438b58f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -55,8 +55,6 @@ Вы уверены, что хотите удалить эту закладку?"" Вы уверены, что хотите удалить эту запись?"" Отмена - Поддержать с помощью - Спасибо за поддержку ❤ У вас нет задач\nНажмите на +, чтобы добавить новую задачу\nили используйте кнопку\nв меню быстрого доступа У вас нет заметок\nНажмите на +, чтобы добавить новую заметку У вас нет записей\nНажмите на +, чтобы добавить новую запись @@ -150,4 +148,14 @@ Вы уверены, что хотите удалить папку и всё её содержимое? Сохранить Редактировать папку + Block screenshots + Export/Import + Export + Successfully exported data to \"Documents/MyBrain Export\" + Something went wrong while exporting + Import data + Successfully imported %1$s + Something went wrong while importing. make sure that the file name and content are not corrupted + Grant permission to export + Importing.... \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a077e575..3d24683b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -55,8 +55,6 @@ 你确定要删除这个书签吗? 你确定要删除这篇日记吗? 取消 - 可选方式: - 感谢你的支持 ❤ 目前没有待办事项\n 点击+按钮创建待办事项\n 或使用快速设置菜单中的快捷方式 目前没有笔记\n 点击+按钮创建笔记 目前没有日记\n 点击+按钮创建日记 @@ -150,4 +148,14 @@ 你确定要删除这个文件夹以及其中的内容吗? 保存 编辑文件夹 + 阻止截屏 + 导出/导入数据 + 导出数据 + 数据已导出至\"Documents/MyBrain Export\" + 导出失败 + 导入数据 + 已成功导入 %1$s + 导入失败,请确认文件名及内容无误 + 获取导出权限 + 导入中.... diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a7e78480..4e0c5fea 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -7,15 +7,15 @@ Dashboard Settings Spaces - App Theme - Light Theme - Dark Theme + App theme + Light theme + Dark theme Auto Startup screen About App version Project on GitHub - Privacy Policy + Privacy policy Product Notes Tasks @@ -26,7 +26,7 @@ Add task to My Brain Search Title - Delete Sub task + Delete sub task Add sub task Description Request a feature / Report a bug @@ -44,19 +44,17 @@ Descending Show completed tasks Search tasks - Save Task - Delete Task - Delete Task? - Delete Note? - Delete Bookmark? - Delete Entry? - Are you sure you want to delete the Task: \n \"%1$s\"" - Are you sure you want to delete the Note: \n \"%1$s\"" - Are you sure you want to delete this bookmark"" - Are you sure you want to delete this entry"" + Save task + Delete task + Delete task? + Delete note? + Delete bookmark? + Delete entry? + Are you sure you want to delete the task: \n \"%1$s\""? + Are you sure you want to delete the note: \n \"%1$s\""? + Are you sure you want to delete this bookmark""? + Are you sure you want to delete this entry""? Cancel - Support with : - Thank you for your support ❤ You don\'t have any tasks\n Click the + button to add a new Task\n or \nby using the shortcut in the quick settings menu You don\'t have any notes\n Click the + button to add a new Note You don\'t have any entries\n Click the + button to add a new entry @@ -65,7 +63,7 @@ List Grid Note content (supports markdown) - Delete Note + Delete note Note cannot be empty Pin note Reading mode @@ -78,8 +76,8 @@ Invalid URL Add bookmark Open link - Search Bookmarks - Delete Bookmark + Search bookmarks + Delete bookmark URL Cancel changes Add entry @@ -107,7 +105,7 @@ You mood during the month You mood during the year %1$d%% - Mood Flow + Mood flow Mood summary "Your mood was " " most of the time " @@ -117,7 +115,7 @@ No events yet Tasks summary You completed - of Tasks in the last Week + of tasks in the last week No tasks yet Loading… Click the refresh button if you already granted the permission @@ -128,7 +126,7 @@ Visit dentist Buy groceries Call mom - App Font + App font System default Delete event Location @@ -138,7 +136,7 @@ Every month Every year Event should not be in the past - Delete Event? + Delete event? Are you sure you want do delete this event? Folders Create folder @@ -150,4 +148,14 @@ Are you sure you want to delete this folder and all of its content? Save Edit folder + Block screenshots + Export/Import + Export data + Successfully exported data to \n\"/Documents/MyBrain Export\" + Something went wrong while exporting + Import data + Successfully imported %1$s + Something went wrong while importing. make sure that the file name and content are not corrupted + Grant permission to export + Importing.... \ No newline at end of file diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index d1dea094..5173332d 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -6,4 +6,7 @@ + + + \ No newline at end of file diff --git a/build.gradle b/build.gradle index 23916553..08a7c546 100644 --- a/build.gradle +++ b/build.gradle @@ -4,8 +4,8 @@ buildscript { } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.2.2' apply false - id 'com.android.library' version '7.2.2' apply false + id 'com.android.application' version '7.3.1' apply false + id 'com.android.library' version '7.3.1' apply false id 'org.jetbrains.kotlin.android' version '1.7.10' apply false id 'com.google.dagger.hilt.android' version '2.43.2' apply false } diff --git a/fastlane/metadata/android/en-US/changelogs/4.txt b/fastlane/metadata/android/en-US/changelogs/4.txt new file mode 100644 index 00000000..786817f1 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4.txt @@ -0,0 +1,8 @@ +- Added export/import data +- Added block screenshots option +- Changed the grid view in notes to staggered grid view +- Added Romanian translation by @SebiTalent04 +- Added Brazilian Portuguese translation by @abChimp +- Added German translation by @SteveMutter +- Fixed recurring events shows only one instance +- Bug fixes \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 055b69d7..b380d695 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Tue May 10 14:12:31 EET 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME