diff --git a/.gitignore b/.gitignore index 7df8dff5..d0dbb42e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,20 @@ # generated files bin/ gen/ - +.gradle/ +build/ +app/build/ +.idea/ # Local configuration file (sdk path, etc) +local.properties project.properties .settings/ .classpath .project + +# Local agent workspace +.codex +.codex/ + +# Local agent instructions +AGENT.md diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 00000000..5d86e776 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,58 @@ +plugins { + id("com.android.application") + id("com.google.devtools.ksp") + id("org.jetbrains.kotlin.plugin.compose") +} + +android { + namespace = "net.micode.notes" + compileSdk = 37 + + defaultConfig { + applicationId = "net.micode.notes" + minSdk = 26 + targetSdk = 37 + versionCode = 1 + versionName = "0.1" + } + + buildTypes { + getByName("release") { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + buildFeatures { + compose = true + } +} + +dependencies { + + + implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) + implementation("androidx.core:core-ktx:1.18.0") + implementation("androidx.room:room-runtime:2.8.4") + implementation("androidx.room:room-ktx:2.8.4") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.10.0") + implementation("androidx.lifecycle:lifecycle-runtime-compose:2.10.0") + implementation("androidx.activity:activity-compose:1.13.0") + implementation("androidx.compose.ui:ui:1.11.0") + implementation("androidx.compose.foundation:foundation:1.11.0") + implementation("androidx.compose.material3:material3:1.4.0") + implementation("androidx.compose.ui:ui-tooling-preview:1.11.0") + implementation("androidx.glance:glance:1.1.1") + implementation("androidx.glance:glance-appwidget:1.1.1") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2") + debugImplementation("androidx.compose.ui:ui-tooling:1.11.0") + ksp("androidx.room:room-compiler:2.8.4") +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..69dd36cc --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1 @@ +# Project-specific ProGuard rules. diff --git a/AndroidManifest.xml b/app/src/main/AndroidManifest.xml similarity index 76% rename from AndroidManifest.xml rename to app/src/main/AndroidManifest.xml index e5c7d47c..a29e4025 100644 --- a/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,14 +15,8 @@ limitations under the License. --> - + - - - @@ -31,6 +25,7 @@ + + android:theme="@style/NoteListComposeTheme" + android:windowSoftInputMode="adjustResize" > @@ -53,8 +47,9 @@ + android:theme="@style/NoteComposeTheme" > @@ -70,23 +65,11 @@ - - - - - - - - @@ -100,6 +83,7 @@ @@ -113,7 +97,9 @@ android:resource="@xml/widget_4x_info" /> - + @@ -121,30 +107,23 @@ + android:theme="@style/NoteComposeTheme" > - - - - - diff --git a/app/src/main/kotlin/net/micode/notes/data/Notes.kt b/app/src/main/kotlin/net/micode/notes/data/Notes.kt new file mode 100644 index 00000000..af76abca --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/data/Notes.kt @@ -0,0 +1,254 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.micode.notes.data + +object Notes { + const val TYPE_NOTE: Int = 0 + const val TYPE_FOLDER: Int = 1 + const val TYPE_SYSTEM: Int = 2 + + /** + * Following IDs are system folders' identifiers + * [Notes.ID_ROOT_FOLDER] is default folder + * [Notes.ID_CALL_RECORD_FOLDER] is to store call records + */ + const val ID_ROOT_FOLDER: Int = 0 + val ID_CALL_RECORD_FOLDER: Int = -2 + val ID_TRASH_FOLER: Int = -3 + + const val INTENT_EXTRA_ALERT_DATE: String = "net.micode.notes.alert_date" + const val INTENT_EXTRA_BACKGROUND_ID: String = "net.micode.notes.background_color_id" + const val INTENT_EXTRA_WIDGET_ID: String = "net.micode.notes.widget_id" + const val INTENT_EXTRA_WIDGET_TYPE: String = "net.micode.notes.widget_type" + const val INTENT_EXTRA_FOLDER_ID: String = "net.micode.notes.folder_id" + const val INTENT_EXTRA_CALL_DATE: String = "net.micode.notes.call_date" + + val TYPE_WIDGET_INVALIDE: Int = -1 + const val TYPE_WIDGET_2X: Int = 0 + const val TYPE_WIDGET_4X: Int = 1 + + interface NoteColumns { + companion object { + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ + const val ID: String = "_id" + + /** + * The parent's id for note or folder + *

Type: INTEGER (long)

+ */ + const val PARENT_ID: String = "parent_id" + + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + const val CREATED_DATE: String = "created_date" + + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + const val MODIFIED_DATE: String = "modified_date" + + + /** + * Alert date + *

Type: INTEGER (long)

+ */ + const val ALERTED_DATE: String = "alert_date" + + /** + * Folder's name or text content of note + *

Type: TEXT

+ */ + const val SNIPPET: String = "snippet" + + /** + * Note's widget id + *

Type: INTEGER (long)

+ */ + const val WIDGET_ID: String = "widget_id" + + /** + * Note's widget type + *

Type: INTEGER (long)

+ */ + const val WIDGET_TYPE: String = "widget_type" + + /** + * Note's background color's id + *

Type: INTEGER (long)

+ */ + const val BG_COLOR_ID: String = "bg_color_id" + + /** + * For text note, it doesn't has attachment, for multi-media + * note, it has at least one attachment + *

Type: INTEGER

+ */ + const val HAS_ATTACHMENT: String = "has_attachment" + + /** + * Folder's count of notes + *

Type: INTEGER (long)

+ */ + const val NOTES_COUNT: String = "notes_count" + + /** + * The file type: folder or note + *

Type: INTEGER

+ */ + const val TYPE: String = "type" + + /** + * The last sync id + *

Type: INTEGER (long)

+ */ + const val SYNC_ID: String = "sync_id" + + /** + * Sign to indicate local modified or not + *

Type: INTEGER

+ */ + const val LOCAL_MODIFIED: String = "local_modified" + + /** + * Original parent id before moving into temporary folder + *

Type : INTEGER

+ */ + const val ORIGIN_PARENT_ID: String = "origin_parent_id" + + /** + * The gtask id + *

Type : TEXT

+ */ + const val GTASK_ID: String = "gtask_id" + + /** + * The version code + *

Type : INTEGER (long)

+ */ + const val VERSION: String = "version" + } + } + + interface DataColumns { + companion object { + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ + const val ID: String = "_id" + + /** + * The MIME type of the item represented by this row. + *

Type: Text

+ */ + const val MIME_TYPE: String = "mime_type" + + /** + * The reference id to note that this data belongs to + *

Type: INTEGER (long)

+ */ + const val NOTE_ID: String = "note_id" + + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + const val CREATED_DATE: String = "created_date" + + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + const val MODIFIED_DATE: String = "modified_date" + + /** + * Data's content + *

Type: TEXT

+ */ + const val CONTENT: String = "content" + + + /** + * Generic data column, the meaning is [.MIMETYPE] specific, used for + * integer data type + *

Type: INTEGER

+ */ + const val DATA1: String = "data1" + + /** + * Generic data column, the meaning is [.MIMETYPE] specific, used for + * integer data type + *

Type: INTEGER

+ */ + const val DATA2: String = "data2" + + /** + * Generic data column, the meaning is [.MIMETYPE] specific, used for + * TEXT data type + *

Type: TEXT

+ */ + const val DATA3: String = "data3" + + /** + * Generic data column, the meaning is [.MIMETYPE] specific, used for + * TEXT data type + *

Type: TEXT

+ */ + const val DATA4: String = "data4" + + /** + * Generic data column, the meaning is [.MIMETYPE] specific, used for + * TEXT data type + *

Type: TEXT

+ */ + const val DATA5: String = "data5" + } + } + + object TextNote : DataColumns { + /** + * Mode to indicate the text in check list mode or not + *

Type: Integer 1:check list mode 0: normal mode

+ */ + val MODE: String = DataColumns.Companion.DATA1 + + const val MODE_CHECK_LIST: Int = 1 + + const val CONTENT_ITEM_TYPE: String = "vnd.android.cursor.item/text_note" + } + + object CallNote : DataColumns { + /** + * Call date for this record + *

Type: INTEGER (long)

+ */ + val CALL_DATE: String = DataColumns.Companion.DATA1 + + /** + * Phone number for this record + *

Type: TEXT

+ */ + val PHONE_NUMBER: String = DataColumns.Companion.DATA3 + + const val CONTENT_ITEM_TYPE: String = "vnd.android.cursor.item/call_note" + } +} diff --git a/app/src/main/kotlin/net/micode/notes/data/NotesRepository.kt b/app/src/main/kotlin/net/micode/notes/data/NotesRepository.kt new file mode 100644 index 00000000..6ece7ba9 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/data/NotesRepository.kt @@ -0,0 +1,352 @@ +package net.micode.notes.data + +import android.content.Context +import android.telephony.PhoneNumberUtils +import androidx.room.withTransaction +import net.micode.notes.data.room.AlarmRow +import net.micode.notes.data.room.CallNoteLookupRow +import net.micode.notes.data.room.NoteListRow +import net.micode.notes.data.room.NotesRoomDatabase +import net.micode.notes.data.room.RoomDataEntity +import net.micode.notes.data.room.RoomNoteEntity +import net.micode.notes.data.room.StoredNotePayload +import net.micode.notes.data.room.WidgetNoteRow +import net.micode.notes.tool.NotesSortMode +import net.micode.notes.ui.AppWidgetAttribute + +data class NoteSaveRequest( + val noteId: Long, + val folderId: Long, + val content: String, + val checkListMode: Int, + val alertDate: Long, + val bgColorId: Int, + val widgetId: Int, + val widgetType: Int, + val modifiedDate: Long, + val callDate: Long?, + val phoneNumber: String? +) + +data class NoteSaveResult( + val noteId: Long, + val modifiedDate: Long +) + +class NotesRepository private constructor(context: Context) { + private val database = NotesRoomDatabase(context.applicationContext) + private val notesDao = database.notesDao() + + suspend fun loadListItems( + folderId: Long, + searchText: String, + sortMode: NotesSortMode + ): List { + val trimmedSearchText = searchText.trim() + val rows = if (folderId == Notes.ID_ROOT_FOLDER.toLong()) { + if (trimmedSearchText.isEmpty()) { + notesDao.getRootListItems( + folderId = folderId, + systemType = Notes.TYPE_SYSTEM, + noteType = Notes.TYPE_NOTE, + callRecordFolderId = Notes.ID_CALL_RECORD_FOLDER.toLong() + ) + } else { + notesDao.searchRootListItems( + folderId = folderId, + systemType = Notes.TYPE_SYSTEM, + noteType = Notes.TYPE_NOTE, + callRecordFolderId = Notes.ID_CALL_RECORD_FOLDER.toLong(), + searchText = "%$trimmedSearchText%" + ) + } + } else { + if (trimmedSearchText.isEmpty()) { + notesDao.getFolderListItems(folderId, Notes.TYPE_NOTE) + } else { + notesDao.searchFolderListItems( + folderId = folderId, + noteType = Notes.TYPE_NOTE, + searchText = "%$trimmedSearchText%" + ) + } + } + return rows.sortedWith(noteListComparator(sortMode)) + } + + suspend fun hasVisibleFolderNamed(name: String): Boolean { + return notesDao.hasVisibleFolderNamed( + name = name, + folderType = Notes.TYPE_FOLDER, + trashFolderId = Notes.ID_TRASH_FOLER.toLong() + ) + } + + suspend fun createFolder(name: String): Long { + val now = System.currentTimeMillis() + return notesDao.insertNote( + RoomNoteEntity( + parentId = Notes.ID_ROOT_FOLDER.toLong(), + createdDate = now, + modifiedDate = now, + snippet = name, + type = Notes.TYPE_FOLDER + ) + ) + } + + suspend fun renameFolder(folderId: Long, name: String): Boolean { + return notesDao.renameFolder( + folderId = folderId, + name = name, + folderType = Notes.TYPE_FOLDER + ) > 0 + } + + suspend fun collectFolderWidgets(folderId: Long): Set { + return notesDao.getFolderWidgets(folderId) + .mapTo(linkedSetOf()) { widget -> + AppWidgetAttribute( + widgetId = widget.widgetId, + widgetType = widget.widgetType + ) + } + } + + suspend fun deleteNotes(noteIds: Set): Boolean { + val validNoteIds = noteIds + .filter { it != Notes.ID_ROOT_FOLDER.toLong() } + .distinct() + if (validNoteIds.isEmpty()) { + return true + } + + return database.withTransaction { + val allNoteIds = notesDao.getDescendantNoteIds(validNoteIds) + if (allNoteIds.isEmpty()) { + return@withTransaction false + } + + notesDao.deleteDataByNoteIds(allNoteIds) + notesDao.deleteNotesByIds(allNoteIds) > 0 + } + } + + suspend fun deleteFolder(folderId: Long): Boolean { + if (folderId == Notes.ID_ROOT_FOLDER.toLong()) { + return false + } + + return deleteNotes(setOf(folderId)) + } + + suspend fun isVisibleNote(noteId: Long, type: Int): Boolean { + return notesDao.isVisibleNote(noteId, type, Notes.ID_TRASH_FOLER.toLong()) + } + + suspend fun loadStoredNote(noteId: Long): StoredNotePayload? { + val note = notesDao.getNoteById(noteId) ?: return null + val dataRows = notesDao.getDataByNoteId(noteId) + return StoredNotePayload( + note = note, + textData = dataRows.firstOrNull { it.mimeType == Notes.TextNote.CONTENT_ITEM_TYPE }, + callData = dataRows.firstOrNull { it.mimeType == Notes.CallNote.CONTENT_ITEM_TYPE } + ) + } + + suspend fun saveNote(request: NoteSaveRequest): NoteSaveResult? { + return database.withTransaction { + val now = request.modifiedDate.takeIf { it > 0L } ?: System.currentTimeMillis() + val existingNote = if (request.noteId > 0L) { + notesDao.getNoteById(request.noteId) + } else { + null + } + if (request.noteId > 0L && existingNote == null) { + return@withTransaction null + } + + val noteId = existingNote?.id ?: notesDao.insertNote( + RoomNoteEntity( + parentId = request.folderId, + alertedDate = request.alertDate, + bgColorId = request.bgColorId, + createdDate = now, + modifiedDate = now, + snippet = request.content, + type = Notes.TYPE_NOTE, + widgetId = request.widgetId, + widgetType = request.widgetType, + localModified = 1 + ) + ) + + if (noteId <= 0L) { + return@withTransaction null + } + + if (existingNote != null) { + notesDao.updateNote( + existingNote.copy( + parentId = request.folderId, + alertedDate = request.alertDate, + bgColorId = request.bgColorId, + modifiedDate = now, + snippet = request.content, + type = Notes.TYPE_NOTE, + widgetId = request.widgetId, + widgetType = request.widgetType, + localModified = 1 + ) + ) + } + + upsertTextData(noteId, request, now) + upsertCallData(noteId, request, now) + + NoteSaveResult( + noteId = noteId, + modifiedDate = now + ) + } + } + + suspend fun getNoteIdByPhoneNumberAndCallDate(phoneNumber: String, callDate: Long): Long { + val candidates = notesDao.findCallNotesByDate( + callDate = callDate, + mimeType = Notes.CallNote.CONTENT_ITEM_TYPE + ) + return candidates.firstOrNull { candidate -> + areSamePhoneNumber(phoneNumber, candidate) + }?.noteId ?: 0L + } + + suspend fun getSnippetById(noteId: Long): String? { + return notesDao.getSnippetById(noteId) + } + + suspend fun getFutureAlertNotes(currentDate: Long): List { + return notesDao.getFutureAlertNotes(currentDate, Notes.TYPE_NOTE) + } + + suspend fun getNotesByWidgetId(appWidgetId: Int): List { + return notesDao.getNotesByWidgetId( + appWidgetId = appWidgetId, + trashFolderId = Notes.ID_TRASH_FOLER.toLong() + ) + } + + suspend fun clearWidgetBinding(appWidgetId: Int, invalidWidgetId: Int): Boolean { + return notesDao.clearWidgetBinding(appWidgetId, invalidWidgetId) > 0 + } + + private suspend fun upsertTextData( + noteId: Long, + request: NoteSaveRequest, + now: Long + ) { + val existingTextData = notesDao.getDataByNoteIdAndMimeType(noteId, Notes.TextNote.CONTENT_ITEM_TYPE) + + val content = request.content + val modeValue = request.checkListMode.toLong() + if (existingTextData == null) { + notesDao.insertData( + RoomDataEntity( + mimeType = Notes.TextNote.CONTENT_ITEM_TYPE, + noteId = noteId, + createdDate = now, + modifiedDate = now, + content = content, + data1 = modeValue + ) + ) + } else { + notesDao.updateData( + existingTextData.copy( + noteId = noteId, + modifiedDate = now, + content = content, + data1 = modeValue + ) + ) + } + } + + private suspend fun upsertCallData( + noteId: Long, + request: NoteSaveRequest, + now: Long + ) { + val existingCallData = notesDao.getDataByNoteIdAndMimeType(noteId, Notes.CallNote.CONTENT_ITEM_TYPE) + if (existingCallData == null && request.phoneNumber.isNullOrBlank() && request.callDate == null) { + return + } + + if (existingCallData == null) { + notesDao.insertData( + RoomDataEntity( + mimeType = Notes.CallNote.CONTENT_ITEM_TYPE, + noteId = noteId, + createdDate = now, + modifiedDate = now, + data1 = request.callDate, + data3 = request.phoneNumber.orEmpty() + ) + ) + } else { + notesDao.updateData( + existingCallData.copy( + noteId = noteId, + modifiedDate = now, + data1 = request.callDate, + data3 = request.phoneNumber.orEmpty() + ) + ) + } + } + + @Suppress("DEPRECATION") + private fun areSamePhoneNumber(phoneNumber: String, candidate: CallNoteLookupRow): Boolean { + return PhoneNumberUtils.compare(phoneNumber, candidate.phoneNumber) + } + + companion object { + private fun noteListComparator(sortMode: NotesSortMode): Comparator { + val groupComparator = compareBy { + when (it.type) { + Notes.TYPE_SYSTEM -> 0 + Notes.TYPE_FOLDER -> 1 + else -> 2 + } + } + val titleSelector: (NoteListRow) -> String = { row -> + row.snippet.trim().lowercase() + } + + return when (sortMode) { + NotesSortMode.MODIFIED_DESC -> groupComparator + .thenByDescending { it.modifiedDate } + .thenBy { titleSelector(it) } + + NotesSortMode.MODIFIED_ASC -> groupComparator + .thenBy { it.modifiedDate } + .thenBy { titleSelector(it) } + + NotesSortMode.TITLE_ASC -> groupComparator + .thenBy { titleSelector(it) } + .thenByDescending { it.modifiedDate } + } + } + + @Volatile + private var instance: NotesRepository? = null + + operator fun invoke(context: Context): NotesRepository { + return instance ?: synchronized(this) { + instance ?: NotesRepository(context.applicationContext).also { repository -> + instance = repository + } + } + } + } +} diff --git a/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomDao.kt b/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomDao.kt new file mode 100644 index 00000000..9acaa131 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomDao.kt @@ -0,0 +1,229 @@ +package net.micode.notes.data.room + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Update + +@Dao +interface NotesRoomDao { + @Query( + """ + SELECT noteItem._id, noteItem.alert_date, noteItem.bg_color_id, noteItem.has_attachment, + noteItem.modified_date, + CASE + WHEN noteItem.type = :noteType THEN 0 + ELSE (SELECT COUNT(*) FROM note AS child WHERE child.parent_id = noteItem._id) + END AS notes_count, + noteItem.parent_id, noteItem.snippet, noteItem.type, noteItem.widget_id, + noteItem.widget_type + FROM note AS noteItem + WHERE ((noteItem.type <> :systemType AND noteItem.parent_id = :folderId) + OR (noteItem._id = :callRecordFolderId + AND EXISTS(SELECT 1 FROM note AS child WHERE child.parent_id = noteItem._id))) + ORDER BY noteItem.type DESC, noteItem.modified_date DESC + """ + ) + suspend fun getRootListItems( + folderId: Long, + systemType: Int, + noteType: Int, + callRecordFolderId: Long + ): List + + @Query( + """ + SELECT noteItem._id, noteItem.alert_date, noteItem.bg_color_id, noteItem.has_attachment, + noteItem.modified_date, + CASE + WHEN noteItem.type = :noteType THEN 0 + ELSE (SELECT COUNT(*) FROM note AS child WHERE child.parent_id = noteItem._id) + END AS notes_count, + noteItem.parent_id, noteItem.snippet, noteItem.type, noteItem.widget_id, + noteItem.widget_type + FROM note AS noteItem + WHERE (((noteItem.type <> :systemType AND noteItem.parent_id = :folderId) + OR (noteItem._id = :callRecordFolderId + AND EXISTS(SELECT 1 FROM note AS child WHERE child.parent_id = noteItem._id))) + AND noteItem.snippet LIKE :searchText) + ORDER BY noteItem.type DESC, noteItem.modified_date DESC + """ + ) + suspend fun searchRootListItems( + folderId: Long, + systemType: Int, + noteType: Int, + callRecordFolderId: Long, + searchText: String + ): List + + @Query( + """ + SELECT noteItem._id, noteItem.alert_date, noteItem.bg_color_id, noteItem.has_attachment, + noteItem.modified_date, + CASE + WHEN noteItem.type = :noteType THEN 0 + ELSE (SELECT COUNT(*) FROM note AS child WHERE child.parent_id = noteItem._id) + END AS notes_count, + noteItem.parent_id, noteItem.snippet, noteItem.type, noteItem.widget_id, + noteItem.widget_type + FROM note AS noteItem + WHERE noteItem.parent_id = :folderId + ORDER BY noteItem.type DESC, noteItem.modified_date DESC + """ + ) + suspend fun getFolderListItems(folderId: Long, noteType: Int): List + + @Query( + """ + SELECT noteItem._id, noteItem.alert_date, noteItem.bg_color_id, noteItem.has_attachment, + noteItem.modified_date, + CASE + WHEN noteItem.type = :noteType THEN 0 + ELSE (SELECT COUNT(*) FROM note AS child WHERE child.parent_id = noteItem._id) + END AS notes_count, + noteItem.parent_id, noteItem.snippet, noteItem.type, noteItem.widget_id, + noteItem.widget_type + FROM note AS noteItem + WHERE noteItem.parent_id = :folderId AND noteItem.snippet LIKE :searchText + ORDER BY noteItem.type DESC, noteItem.modified_date DESC + """ + ) + suspend fun searchFolderListItems(folderId: Long, noteType: Int, searchText: String): List + + @Query( + """ + SELECT EXISTS( + SELECT 1 + FROM note + WHERE type = :folderType + AND parent_id <> :trashFolderId + AND snippet = :name + ) + """ + ) + suspend fun hasVisibleFolderNamed( + name: String, + folderType: Int, + trashFolderId: Long + ): Boolean + + @Insert + suspend fun insertNote(note: RoomNoteEntity): Long + + @Update + suspend fun updateNote(note: RoomNoteEntity): Int + + @Query("SELECT * FROM note WHERE _id = :noteId LIMIT 1") + suspend fun getNoteById(noteId: Long): RoomNoteEntity? + + @Query( + """ + UPDATE note + SET snippet = :name, + type = :folderType, + local_modified = 1 + WHERE _id = :folderId + """ + ) + suspend fun renameFolder(folderId: Long, name: String, folderType: Int): Int + + @Query( + """ + WITH RECURSIVE descendants(_id) AS ( + SELECT _id + FROM note + WHERE _id IN (:rootIds) + UNION + SELECT child._id + FROM note AS child + INNER JOIN descendants ON child.parent_id = descendants._id + ) + SELECT _id FROM descendants + """ + ) + suspend fun getDescendantNoteIds(rootIds: List): List + + @Query("DELETE FROM data WHERE note_id IN (:noteIds)") + suspend fun deleteDataByNoteIds(noteIds: List): Int + + @Query("DELETE FROM note WHERE _id IN (:noteIds)") + suspend fun deleteNotesByIds(noteIds: List): Int + + @Query( + """ + SELECT widget_id, widget_type + FROM note + WHERE parent_id = :folderId + """ + ) + suspend fun getFolderWidgets(folderId: Long): List + + @Query( + """ + SELECT EXISTS( + SELECT 1 + FROM note + WHERE _id = :noteId + AND type = :type + AND parent_id <> :trashFolderId + ) + """ + ) + suspend fun isVisibleNote(noteId: Long, type: Int, trashFolderId: Long): Boolean + + @Query("SELECT snippet FROM note WHERE _id = :noteId LIMIT 1") + suspend fun getSnippetById(noteId: Long): String? + + @Query( + """ + SELECT _id AS noteId, alert_date + FROM note + WHERE alert_date > :currentDate + AND type = :noteType + """ + ) + suspend fun getFutureAlertNotes(currentDate: Long, noteType: Int): List + + @Query( + """ + SELECT _id, bg_color_id, snippet + FROM note + WHERE widget_id = :appWidgetId + AND parent_id <> :trashFolderId + """ + ) + suspend fun getNotesByWidgetId(appWidgetId: Int, trashFolderId: Long): List + + @Query( + """ + UPDATE note + SET widget_id = :invalidWidgetId + WHERE widget_id = :appWidgetId + """ + ) + suspend fun clearWidgetBinding(appWidgetId: Int, invalidWidgetId: Int): Int + + @Query("SELECT * FROM data WHERE note_id = :noteId") + suspend fun getDataByNoteId(noteId: Long): List + + @Query("SELECT * FROM data WHERE note_id = :noteId AND mime_type = :mimeType LIMIT 1") + suspend fun getDataByNoteIdAndMimeType(noteId: Long, mimeType: String): RoomDataEntity? + + @Insert + suspend fun insertData(data: RoomDataEntity): Long + + @Update + suspend fun updateData(data: RoomDataEntity): Int + + @Query( + """ + SELECT note_id AS noteId, data3 AS phoneNumber + FROM data + WHERE data1 = :callDate + AND mime_type = :mimeType + """ + ) + suspend fun findCallNotesByDate(callDate: Long, mimeType: String): List + +} diff --git a/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomDatabase.kt b/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomDatabase.kt new file mode 100644 index 00000000..8210e8bc --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomDatabase.kt @@ -0,0 +1,126 @@ +package net.micode.notes.data.room + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase +import net.micode.notes.data.Notes + +@Database( + entities = [RoomNoteEntity::class, RoomDataEntity::class], + version = 4, + exportSchema = false +) +abstract class NotesRoomDatabase : RoomDatabase() { + abstract fun notesDao(): NotesRoomDao + + companion object { + private const val DB_NAME = "note.db" + private const val CREATE_NOTE_TABLE_SQL = """ + CREATE TABLE IF NOT EXISTS note ( + _id INTEGER PRIMARY KEY, + parent_id INTEGER NOT NULL DEFAULT 0, + alert_date INTEGER NOT NULL DEFAULT 0, + bg_color_id INTEGER NOT NULL DEFAULT 0, + created_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000), + has_attachment INTEGER NOT NULL DEFAULT 0, + modified_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000), + notes_count INTEGER NOT NULL DEFAULT 0, + snippet TEXT NOT NULL DEFAULT '', + type INTEGER NOT NULL DEFAULT 0, + widget_id INTEGER NOT NULL DEFAULT 0, + widget_type INTEGER NOT NULL DEFAULT -1, + sync_id INTEGER NOT NULL DEFAULT 0, + local_modified INTEGER NOT NULL DEFAULT 0, + origin_parent_id INTEGER NOT NULL DEFAULT 0, + gtask_id TEXT NOT NULL DEFAULT '', + version INTEGER NOT NULL DEFAULT 0 + ) + """ + private const val CREATE_DATA_TABLE_SQL = """ + CREATE TABLE IF NOT EXISTS data ( + _id INTEGER PRIMARY KEY, + mime_type TEXT NOT NULL, + note_id INTEGER NOT NULL DEFAULT 0, + created_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000), + modified_date INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000), + content TEXT NOT NULL DEFAULT '', + data1 INTEGER, + data2 INTEGER, + data3 TEXT NOT NULL DEFAULT '', + data4 TEXT NOT NULL DEFAULT '', + data5 TEXT NOT NULL DEFAULT '' + ) + """ + private const val CREATE_DATA_NOTE_ID_INDEX_SQL = + "CREATE INDEX IF NOT EXISTS note_id_index ON data(note_id)" + + @Volatile + private var instance: NotesRoomDatabase? = null + + operator fun invoke(context: Context): NotesRoomDatabase { + return instance ?: synchronized(this) { + instance ?: buildDatabase(context.applicationContext).also { db -> + instance = db + } + } + } + + private fun buildDatabase(context: Context): NotesRoomDatabase { + return Room.databaseBuilder(context, NotesRoomDatabase::class.java, DB_NAME) + .addMigrations(MIGRATION_1_4, MIGRATION_2_4, MIGRATION_3_4) + .addCallback(SystemFoldersCallback) + .build() + } + + private val SystemFoldersCallback = object : RoomDatabase.Callback() { + override fun onOpen(db: SupportSQLiteDatabase) { + super.onOpen(db) + insertSystemFolders(db) + } + } + + private val MIGRATION_1_4 = object : Migration(1, 4) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("DROP TABLE IF EXISTS note") + db.execSQL("DROP TABLE IF EXISTS data") + createSchema(db) + insertSystemFolders(db) + } + } + + private val MIGRATION_2_4 = object : Migration(2, 4) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE note ADD COLUMN gtask_id TEXT NOT NULL DEFAULT ''") + db.execSQL("ALTER TABLE note ADD COLUMN version INTEGER NOT NULL DEFAULT 0") + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL) + insertSystemFolders(db) + } + } + + private val MIGRATION_3_4 = object : Migration(3, 4) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE note ADD COLUMN version INTEGER NOT NULL DEFAULT 0") + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL) + insertSystemFolders(db) + } + } + + private fun createSchema(db: SupportSQLiteDatabase) { + db.execSQL(CREATE_NOTE_TABLE_SQL) + db.execSQL(CREATE_DATA_TABLE_SQL) + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL) + } + + private fun insertSystemFolders(db: SupportSQLiteDatabase) { + db.execSQL( + "INSERT OR IGNORE INTO note (_id, type) VALUES " + + "(${Notes.ID_CALL_RECORD_FOLDER}, ${Notes.TYPE_SYSTEM}), " + + "(${Notes.ID_ROOT_FOLDER}, ${Notes.TYPE_SYSTEM}), " + + "(${Notes.ID_TRASH_FOLER}, ${Notes.TYPE_SYSTEM})" + ) + } + } +} diff --git a/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomEntities.kt b/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomEntities.kt new file mode 100644 index 00000000..887edacd --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomEntities.kt @@ -0,0 +1,76 @@ +package net.micode.notes.data.room + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import net.micode.notes.data.Notes + +@Entity(tableName = "note") +data class RoomNoteEntity( + @PrimaryKey + @ColumnInfo(name = Notes.NoteColumns.ID) + val id: Long? = null, + @ColumnInfo(name = Notes.NoteColumns.PARENT_ID, defaultValue = "0") + val parentId: Long = 0, + @ColumnInfo(name = Notes.NoteColumns.ALERTED_DATE, defaultValue = "0") + val alertedDate: Long = 0, + @ColumnInfo(name = Notes.NoteColumns.BG_COLOR_ID, defaultValue = "0") + val bgColorId: Int = 0, + @ColumnInfo(name = Notes.NoteColumns.CREATED_DATE, defaultValue = "(strftime('%s','now') * 1000)") + val createdDate: Long = 0, + @ColumnInfo(name = Notes.NoteColumns.HAS_ATTACHMENT, defaultValue = "0") + val hasAttachment: Int = 0, + @ColumnInfo(name = Notes.NoteColumns.MODIFIED_DATE, defaultValue = "(strftime('%s','now') * 1000)") + val modifiedDate: Long = 0, + @ColumnInfo(name = Notes.NoteColumns.NOTES_COUNT, defaultValue = "0") + val notesCount: Int = 0, + @ColumnInfo(name = Notes.NoteColumns.SNIPPET, defaultValue = "''") + val snippet: String = "", + @ColumnInfo(name = Notes.NoteColumns.TYPE, defaultValue = "0") + val type: Int = 0, + @ColumnInfo(name = Notes.NoteColumns.WIDGET_ID, defaultValue = "0") + val widgetId: Int = 0, + @ColumnInfo(name = Notes.NoteColumns.WIDGET_TYPE, defaultValue = "-1") + val widgetType: Int = -1, + @ColumnInfo(name = Notes.NoteColumns.SYNC_ID, defaultValue = "0") + val syncId: Long = 0, + @ColumnInfo(name = Notes.NoteColumns.LOCAL_MODIFIED, defaultValue = "0") + val localModified: Int = 0, + @ColumnInfo(name = Notes.NoteColumns.ORIGIN_PARENT_ID, defaultValue = "0") + val originParentId: Long = 0, + @ColumnInfo(name = Notes.NoteColumns.GTASK_ID, defaultValue = "''") + val gtaskId: String = "", + @ColumnInfo(name = Notes.NoteColumns.VERSION, defaultValue = "0") + val version: Long = 0 +) + +@Entity( + tableName = "data", + indices = [Index(value = [Notes.DataColumns.NOTE_ID], name = "note_id_index")] +) +data class RoomDataEntity( + @PrimaryKey + @ColumnInfo(name = Notes.DataColumns.ID) + val id: Long? = null, + @ColumnInfo(name = Notes.DataColumns.MIME_TYPE) + val mimeType: String, + @ColumnInfo(name = Notes.DataColumns.NOTE_ID, defaultValue = "0") + val noteId: Long = 0, + @ColumnInfo(name = Notes.DataColumns.CREATED_DATE, defaultValue = "(strftime('%s','now') * 1000)") + val createdDate: Long = 0, + @ColumnInfo(name = Notes.DataColumns.MODIFIED_DATE, defaultValue = "(strftime('%s','now') * 1000)") + val modifiedDate: Long = 0, + @ColumnInfo(name = Notes.DataColumns.CONTENT, defaultValue = "''") + val content: String = "", + @ColumnInfo(name = Notes.DataColumns.DATA1) + val data1: Long? = null, + @ColumnInfo(name = Notes.DataColumns.DATA2) + val data2: Long? = null, + @ColumnInfo(name = Notes.DataColumns.DATA3, defaultValue = "''") + val data3: String = "", + @ColumnInfo(name = Notes.DataColumns.DATA4, defaultValue = "''") + val data4: String = "", + @ColumnInfo(name = Notes.DataColumns.DATA5, defaultValue = "''") + val data5: String = "" +) diff --git a/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomModels.kt b/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomModels.kt new file mode 100644 index 00000000..222acde9 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/data/room/NotesRoomModels.kt @@ -0,0 +1,63 @@ +package net.micode.notes.data.room + +import androidx.room.ColumnInfo +import net.micode.notes.data.Notes + +data class NoteListRow( + @ColumnInfo(name = Notes.NoteColumns.ID) + val id: Long, + @ColumnInfo(name = Notes.NoteColumns.ALERTED_DATE) + val alertedDate: Long, + @ColumnInfo(name = Notes.NoteColumns.BG_COLOR_ID) + val bgColorId: Int, + @ColumnInfo(name = Notes.NoteColumns.HAS_ATTACHMENT) + val hasAttachment: Int, + @ColumnInfo(name = Notes.NoteColumns.MODIFIED_DATE) + val modifiedDate: Long, + @ColumnInfo(name = Notes.NoteColumns.NOTES_COUNT) + val notesCount: Int, + @ColumnInfo(name = Notes.NoteColumns.PARENT_ID) + val parentId: Long, + @ColumnInfo(name = Notes.NoteColumns.SNIPPET) + val snippet: String, + @ColumnInfo(name = Notes.NoteColumns.TYPE) + val type: Int, + @ColumnInfo(name = Notes.NoteColumns.WIDGET_ID) + val widgetId: Int, + @ColumnInfo(name = Notes.NoteColumns.WIDGET_TYPE) + val widgetType: Int +) + +data class WidgetRow( + @ColumnInfo(name = Notes.NoteColumns.WIDGET_ID) + val widgetId: Int, + @ColumnInfo(name = Notes.NoteColumns.WIDGET_TYPE) + val widgetType: Int +) + +data class AlarmRow( + @ColumnInfo(name = "noteId") + val noteId: Long, + @ColumnInfo(name = Notes.NoteColumns.ALERTED_DATE) + val alertedDate: Long +) + +data class WidgetNoteRow( + @ColumnInfo(name = Notes.NoteColumns.ID) + val id: Long, + @ColumnInfo(name = Notes.NoteColumns.BG_COLOR_ID) + val bgColorId: Int, + @ColumnInfo(name = Notes.NoteColumns.SNIPPET) + val snippet: String +) + +data class CallNoteLookupRow( + val noteId: Long, + val phoneNumber: String +) + +data class StoredNotePayload( + val note: RoomNoteEntity, + val textData: RoomDataEntity?, + val callData: RoomDataEntity? +) diff --git a/app/src/main/kotlin/net/micode/notes/model/WorkingNote.kt b/app/src/main/kotlin/net/micode/notes/model/WorkingNote.kt new file mode 100644 index 00000000..e07eab59 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/model/WorkingNote.kt @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.micode.notes.model + +import android.content.Context +import android.text.TextUtils +import android.util.Log +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import net.micode.notes.data.NoteSaveRequest +import net.micode.notes.data.Notes +import net.micode.notes.data.NotesRepository +import net.micode.notes.tool.ResourceParser.NoteBgResources + +class WorkingNote { + var noteId: Long + private set + + var content: String? = null + private set + + private var mode = 0 + + var alertDate: Long = 0 + private set + + var modifiedDate: Long = 0 + private set + + private var bgColorIdInternal = 0 + private var widgetIdInternal = 0 + private var widgetTypeInternal = Notes.TYPE_WIDGET_INVALIDE + private var callDate: Long? = null + private var phoneNumber: String? = null + + var folderId: Long + private set + + private val repository: NotesRepository + private var isDeleted: Boolean + private var hasLocalChanges = false + + private constructor(context: Context, folderId: Long) { + repository = NotesRepository(context.applicationContext) + alertDate = 0 + modifiedDate = System.currentTimeMillis() + this.folderId = folderId + noteId = 0 + isDeleted = false + } + + private constructor(context: Context, noteId: Long, folderId: Long) { + repository = NotesRepository(context.applicationContext) + this.noteId = noteId + this.folderId = folderId + isDeleted = false + loadNote() + } + + private fun loadNote() { + val storedNote = runBlocking(Dispatchers.IO) { + repository.loadStoredNote(noteId) + } ?: run { + Log.e(TAG, "No note with id: $noteId") + throw IllegalArgumentException("Unable to find note with id $noteId") + } + + folderId = storedNote.note.parentId + bgColorIdInternal = storedNote.note.bgColorId + widgetIdInternal = storedNote.note.widgetId + widgetTypeInternal = storedNote.note.widgetType + alertDate = storedNote.note.alertedDate + modifiedDate = storedNote.note.modifiedDate + + content = storedNote.textData?.content + mode = storedNote.textData?.data1?.toInt() ?: 0 + + callDate = storedNote.callData?.data1 + phoneNumber = storedNote.callData?.data3 + hasLocalChanges = false + } + + @Synchronized + fun saveNote(): Boolean { + if (!isWorthSaving) { + return false + } + + val result = runBlocking(Dispatchers.IO) { + repository.saveNote( + NoteSaveRequest( + noteId = noteId, + folderId = folderId, + content = content.orEmpty(), + checkListMode = mode, + alertDate = alertDate, + bgColorId = bgColorIdInternal, + widgetId = widgetIdInternal, + widgetType = widgetTypeInternal, + modifiedDate = modifiedDate, + callDate = callDate, + phoneNumber = phoneNumber + ) + ) + } ?: return false + + noteId = result.noteId + modifiedDate = result.modifiedDate + hasLocalChanges = false + return true + } + + fun existInDatabase(): Boolean = noteId > 0 + + private val isWorthSaving: Boolean + get() { + return !(isDeleted || (!existInDatabase() && TextUtils.isEmpty(content) && + callDate == null && phoneNumber.isNullOrBlank()) || + (existInDatabase() && !hasLocalChanges)) + } + + fun setAlertDate(date: Long) { + if (date != alertDate) { + alertDate = date + markDirty() + } + } + + fun markDeleted() { + isDeleted = true + } + + fun setWorkingText(text: String?) { + if (!TextUtils.equals(content, text)) { + content = text + markDirty() + } + } + + fun convertToCallNote(phoneNumber: String?, callDate: Long) { + this.phoneNumber = phoneNumber + this.callDate = callDate + folderId = Notes.ID_CALL_RECORD_FOLDER.toLong() + markDirty() + } + + val bgColorResId: Int + get() = NoteBgResources.getNoteBgResource(bgColorIdInternal) + + var bgColorId: Int + get() = bgColorIdInternal + set(id) { + if (id != bgColorIdInternal) { + bgColorIdInternal = id + markDirty() + } + } + + val titleBgResId: Int + get() = NoteBgResources.getNoteTitleBgResource(bgColorIdInternal) + + var checkListMode: Int + get() = mode + set(value) { + if (mode != value) { + mode = value + markDirty() + } + } + + var widgetId: Int + get() = widgetIdInternal + set(id) { + if (id != widgetIdInternal) { + widgetIdInternal = id + markDirty() + } + } + + var widgetType: Int + get() = widgetTypeInternal + set(type) { + if (type != widgetTypeInternal) { + widgetTypeInternal = type + markDirty() + } + } + + private fun markDirty() { + hasLocalChanges = true + modifiedDate = System.currentTimeMillis() + } + + companion object { + private const val TAG = "WorkingNote" + + fun createEmptyNote( + context: Context, + folderId: Long, + widgetId: Int, + widgetType: Int, + defaultBgColorId: Int + ): WorkingNote { + val note = WorkingNote(context, folderId) + note.bgColorId = defaultBgColorId + note.widgetId = widgetId + note.widgetType = widgetType + return note + } + + fun load(context: Context, id: Long): WorkingNote { + return WorkingNote(context, id, 0) + } + } +} diff --git a/app/src/main/kotlin/net/micode/notes/tool/DefaultPreferences.kt b/app/src/main/kotlin/net/micode/notes/tool/DefaultPreferences.kt new file mode 100644 index 00000000..37c5a2d4 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/tool/DefaultPreferences.kt @@ -0,0 +1,9 @@ +package net.micode.notes.tool + +import android.content.Context +import android.content.SharedPreferences + +private const val DEFAULT_PREFERENCES_SUFFIX = "_preferences" + +fun Context.defaultPreferences(): SharedPreferences = + getSharedPreferences("${packageName}$DEFAULT_PREFERENCES_SUFFIX", Context.MODE_PRIVATE) diff --git a/app/src/main/kotlin/net/micode/notes/tool/NotesPreferences.kt b/app/src/main/kotlin/net/micode/notes/tool/NotesPreferences.kt new file mode 100644 index 00000000..966cd81d --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/tool/NotesPreferences.kt @@ -0,0 +1,124 @@ +package net.micode.notes.tool + +import android.content.Context +import androidx.core.content.edit +import net.micode.notes.data.Notes + +enum class NotesSortMode(val value: Int) { + MODIFIED_DESC(0), + MODIFIED_ASC(1), + TITLE_ASC(2); + + companion object { + fun fromValue(value: Int): NotesSortMode { + return values().firstOrNull { it.value == value } ?: MODIFIED_DESC + } + } +} + +object NotesPreferences { + private const val KEY_RANDOM_BACKGROUND = "pref_key_bg_random_appear" + private const val KEY_DEFAULT_BACKGROUND = "pref_key_default_bg_color" + private const val KEY_LIST_SORT_MODE = "pref_key_list_sort_mode" + private const val KEY_DELETE_CONFIRMATION = "pref_key_delete_confirmation" + private const val KEY_REMEMBER_LAST_FOLDER = "pref_key_remember_last_folder" + private const val KEY_LAST_FOLDER_ID = "pref_key_last_folder_id" + private const val KEY_LAST_FOLDER_TITLE = "pref_key_last_folder_title" + + fun isRandomBackgroundEnabled(context: Context): Boolean { + return context.defaultPreferences().getBoolean(KEY_RANDOM_BACKGROUND, false) + } + + fun setRandomBackgroundEnabled(context: Context, enabled: Boolean) { + context.defaultPreferences().edit { putBoolean(KEY_RANDOM_BACKGROUND, enabled) } + } + + fun getDefaultBackgroundColor(context: Context): Int { + val colorId = context.defaultPreferences() + .getInt(KEY_DEFAULT_BACKGROUND, ResourceParser.BG_DEFAULT_COLOR) + return if (colorId in 0 until ResourceParser.NoteBgResources.resourcesSize) { + colorId + } else { + ResourceParser.BG_DEFAULT_COLOR + } + } + + fun setDefaultBackgroundColor(context: Context, colorId: Int) { + context.defaultPreferences().edit { + putInt(KEY_DEFAULT_BACKGROUND, getDefaultBackgroundColorId(colorId)) + } + } + + fun getListSortMode(context: Context): NotesSortMode { + val value = context.defaultPreferences().getInt( + KEY_LIST_SORT_MODE, + NotesSortMode.MODIFIED_DESC.value + ) + return NotesSortMode.fromValue(value) + } + + fun setListSortMode(context: Context, sortMode: NotesSortMode) { + context.defaultPreferences().edit { putInt(KEY_LIST_SORT_MODE, sortMode.value) } + } + + fun isDeleteConfirmationEnabled(context: Context): Boolean { + return context.defaultPreferences().getBoolean(KEY_DELETE_CONFIRMATION, true) + } + + fun setDeleteConfirmationEnabled(context: Context, enabled: Boolean) { + context.defaultPreferences().edit { putBoolean(KEY_DELETE_CONFIRMATION, enabled) } + } + + fun isRememberLastFolderEnabled(context: Context): Boolean { + return context.defaultPreferences().getBoolean(KEY_REMEMBER_LAST_FOLDER, true) + } + + fun setRememberLastFolderEnabled(context: Context, enabled: Boolean) { + context.defaultPreferences().edit { putBoolean(KEY_REMEMBER_LAST_FOLDER, enabled) } + if (!enabled) { + clearRememberedFolder(context) + } + } + + fun getRememberedFolderId(context: Context): Long { + if (!isRememberLastFolderEnabled(context)) { + return Notes.ID_ROOT_FOLDER.toLong() + } + return context.defaultPreferences() + .getLong(KEY_LAST_FOLDER_ID, Notes.ID_ROOT_FOLDER.toLong()) + } + + fun getRememberedFolderTitle(context: Context): String { + if (!isRememberLastFolderEnabled(context)) { + return "" + } + return context.defaultPreferences().getString(KEY_LAST_FOLDER_TITLE, "").orEmpty() + } + + fun rememberLastFolder(context: Context, folderId: Long, folderTitle: String) { + if (!isRememberLastFolderEnabled(context)) { + clearRememberedFolder(context) + return + } + + context.defaultPreferences().edit { + putLong(KEY_LAST_FOLDER_ID, folderId) + putString(KEY_LAST_FOLDER_TITLE, folderTitle) + } + } + + fun clearRememberedFolder(context: Context) { + context.defaultPreferences().edit { + putLong(KEY_LAST_FOLDER_ID, Notes.ID_ROOT_FOLDER.toLong()) + putString(KEY_LAST_FOLDER_TITLE, "") + } + } + + private fun getDefaultBackgroundColorId(colorId: Int): Int { + return if (colorId in 0 until ResourceParser.NoteBgResources.resourcesSize) { + colorId + } else { + ResourceParser.BG_DEFAULT_COLOR + } + } +} diff --git a/src/net/micode/notes/ui/AlarmReceiver.java b/app/src/main/kotlin/net/micode/notes/tool/PendingIntentCompat.kt similarity index 61% rename from src/net/micode/notes/ui/AlarmReceiver.java rename to app/src/main/kotlin/net/micode/notes/tool/PendingIntentCompat.kt index 54e503bd..48495c01 100644 --- a/src/net/micode/notes/ui/AlarmReceiver.java +++ b/app/src/main/kotlin/net/micode/notes/tool/PendingIntentCompat.kt @@ -13,18 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package net.micode.notes.tool -package net.micode.notes.ui; +import android.app.PendingIntent -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; +object PendingIntentCompat { + fun immutableFlag(): Int = PendingIntent.FLAG_IMMUTABLE -public class AlarmReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - intent.setClass(context, AlarmAlertActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); + fun updateCurrentImmutableFlag(): Int { + return PendingIntent.FLAG_UPDATE_CURRENT or immutableFlag() } } diff --git a/app/src/main/kotlin/net/micode/notes/tool/ResourceParser.kt b/app/src/main/kotlin/net/micode/notes/tool/ResourceParser.kt new file mode 100644 index 00000000..ac744128 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/tool/ResourceParser.kt @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.micode.notes.tool + +import android.content.Context +import net.micode.notes.R +import kotlin.random.Random + +object ResourceParser { + const val YELLOW: Int = 0 + const val BLUE: Int = 1 + const val WHITE: Int = 2 + const val GREEN: Int = 3 + const val RED: Int = 4 + + val BG_DEFAULT_COLOR: Int = YELLOW + + const val TEXT_SMALL: Int = 0 + const val TEXT_MEDIUM: Int = 1 + const val TEXT_LARGE: Int = 2 + const val TEXT_SUPER: Int = 3 + + val BG_DEFAULT_FONT_SIZE: Int = TEXT_MEDIUM + + fun getDefaultBgId(context: Context): Int { + return if (NotesPreferences.isRandomBackgroundEnabled(context)) { + Random.nextInt(NoteBgResources.resourcesSize) + } else { + NotesPreferences.getDefaultBackgroundColor(context) + } + } + + object NoteBgResources { + private val BG_EDIT_RESOURCES = intArrayOf( + R.drawable.edit_yellow, + R.drawable.edit_blue, + R.drawable.edit_white, + R.drawable.edit_green, + R.drawable.edit_red + ) + + private val BG_EDIT_TITLE_RESOURCES = intArrayOf( + R.drawable.edit_title_yellow, + R.drawable.edit_title_blue, + R.drawable.edit_title_white, + R.drawable.edit_title_green, + R.drawable.edit_title_red + ) + + fun getNoteBgResource(id: Int): Int { + return BG_EDIT_RESOURCES[safeIndex(id, BG_EDIT_RESOURCES.size)] + } + + fun getNoteTitleBgResource(id: Int): Int { + return BG_EDIT_TITLE_RESOURCES[safeIndex(id, BG_EDIT_TITLE_RESOURCES.size)] + } + + val resourcesSize: Int + get() = BG_EDIT_RESOURCES.size + } + + object WidgetBgResources { + private val BG_2X_RESOURCES = intArrayOf( + R.drawable.widget_2x_yellow, + R.drawable.widget_2x_blue, + R.drawable.widget_2x_white, + R.drawable.widget_2x_green, + R.drawable.widget_2x_red, + ) + + fun getWidget2xBgResource(id: Int): Int { + return BG_2X_RESOURCES[safeIndex(id, BG_2X_RESOURCES.size)] + } + + private val BG_4X_RESOURCES = intArrayOf( + R.drawable.widget_4x_yellow, + R.drawable.widget_4x_blue, + R.drawable.widget_4x_white, + R.drawable.widget_4x_green, + R.drawable.widget_4x_red + ) + + fun getWidget4xBgResource(id: Int): Int { + return BG_4X_RESOURCES[safeIndex(id, BG_4X_RESOURCES.size)] + } + } + + private fun safeIndex(id: Int, size: Int): Int { + return if (id in 0 until size) id else BG_DEFAULT_COLOR + } +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/AlarmAlertActivity.kt b/app/src/main/kotlin/net/micode/notes/ui/AlarmAlertActivity.kt new file mode 100644 index 00000000..0ca58b5f --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/AlarmAlertActivity.kt @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.micode.notes.ui + +import android.app.Activity +import android.app.AlertDialog +import android.content.DialogInterface +import android.content.Intent +import android.media.AudioAttributes +import android.media.MediaPlayer +import android.media.RingtoneManager +import android.os.Build +import android.os.Bundle +import android.os.PowerManager +import android.view.Window +import android.view.WindowManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import net.micode.notes.R +import net.micode.notes.data.Notes +import net.micode.notes.data.NotesRepository +import java.io.IOException + +class AlarmAlertActivity : Activity(), DialogInterface.OnClickListener, + DialogInterface.OnDismissListener { + private var mNoteId: Long = 0 + private var mSnippet: String? = null + var mPlayer: MediaPlayer? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestWindowFeature(Window.FEATURE_NO_TITLE) + configureWindowForAlarm() + + val intent = intent + + try { + mNoteId = intent.getLongExtra(Intent.EXTRA_UID, 0L).takeIf { it > 0L } + ?: intent.data?.pathSegments?.getOrNull(1)?.toLongOrNull() + ?: throw IllegalArgumentException("Missing note id in alarm intent") + val repository = NotesRepository(applicationContext) + val snippet = runBlocking(Dispatchers.IO) { + repository.getSnippetById(mNoteId).orEmpty() + } + mSnippet = if (snippet.length > SNIPPET_PREW_MAX_LEN) + snippet.substring( + 0, + SNIPPET_PREW_MAX_LEN + ) + getResources().getString(R.string.notelist_string_info) + else + snippet + } catch (e: IllegalArgumentException) { + e.printStackTrace() + return + } + + mPlayer = MediaPlayer() + val visible = runBlocking(Dispatchers.IO) { + NotesRepository(applicationContext).isVisibleNote(mNoteId, Notes.TYPE_NOTE) + } + if (visible) { + showActionDialog() + playAlarmSound() + } else { + finish() + } + } + + private val isScreenOn: Boolean + get() = (getSystemService(POWER_SERVICE) as PowerManager).isInteractive + + @Suppress("DEPRECATION") + private fun configureWindowForAlarm() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) { + setShowWhenLocked(true) + setTurnScreenOn(true) + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + return + } + + window.addFlags( + WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or + WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON or + WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or + WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + ) + } + + private fun playAlarmSound() { + val url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM) + val player = mPlayer ?: return + player.setAudioAttributes( + AudioAttributes.Builder() + .setUsage(AudioAttributes.USAGE_ALARM) + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .build() + ) + try { + player.setDataSource(this, url) + player.prepare() + player.isLooping = true + player.start() + } catch (e: IllegalArgumentException) { + // TODO Auto-generated catch block + e.printStackTrace() + } catch (e: SecurityException) { + // TODO Auto-generated catch block + e.printStackTrace() + } catch (e: IllegalStateException) { + // TODO Auto-generated catch block + e.printStackTrace() + } catch (e: IOException) { + e.printStackTrace() + } + } + + private fun showActionDialog() { + val dialog = AlertDialog.Builder(this) + dialog.setTitle(R.string.app_name) + dialog.setMessage(mSnippet) + dialog.setPositiveButton(R.string.notealert_ok, this) + if (this.isScreenOn) { + dialog.setNegativeButton(R.string.notealert_enter, this) + } + dialog.show().setOnDismissListener(this) + } + + override fun onClick(dialog: DialogInterface?, which: Int) { + when (which) { + DialogInterface.BUTTON_NEGATIVE -> { + val intent = Intent(this, NoteEditActivity::class.java) + intent.setAction(Intent.ACTION_VIEW) + intent.putExtra(Intent.EXTRA_UID, mNoteId) + startActivity(intent) + } + + else -> {} + } + } + + override fun onDismiss(dialog: DialogInterface?) { + stopAlarmSound() + finish() + } + + private fun stopAlarmSound() { + mPlayer?.let { player -> + player.stop() + player.release() + mPlayer = null + } + } + + companion object { + private const val SNIPPET_PREW_MAX_LEN = 60 + } +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/AlarmInitReceiver.kt b/app/src/main/kotlin/net/micode/notes/ui/AlarmInitReceiver.kt new file mode 100644 index 00000000..e390a65f --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/AlarmInitReceiver.kt @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.micode.notes.ui + +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import net.micode.notes.data.NotesRepository +import net.micode.notes.tool.PendingIntentCompat + +class AlarmInitReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent?) { + if (intent?.action != Intent.ACTION_BOOT_COMPLETED) { + return + } + + val currentDate = System.currentTimeMillis() + val alarms = runBlocking(Dispatchers.IO) { + NotesRepository(context.applicationContext).getFutureAlertNotes(currentDate) + } + val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarms.forEach { alarm -> + val sender = Intent(context, AlarmReceiver::class.java).apply { + putExtra(Intent.EXTRA_UID, alarm.noteId) + } + val pendingIntent = PendingIntent.getBroadcast( + context, + 0, + sender, + PendingIntentCompat.immutableFlag() + ) + alarmManager.set(AlarmManager.RTC_WAKEUP, alarm.alertedDate, pendingIntent) + } + } +} diff --git a/src/net/micode/notes/gtask/exception/NetworkFailureException.java b/app/src/main/kotlin/net/micode/notes/ui/AlarmReceiver.kt similarity index 59% rename from src/net/micode/notes/gtask/exception/NetworkFailureException.java rename to app/src/main/kotlin/net/micode/notes/ui/AlarmReceiver.kt index b08cfb1d..1c17b78c 100644 --- a/src/net/micode/notes/gtask/exception/NetworkFailureException.java +++ b/app/src/main/kotlin/net/micode/notes/ui/AlarmReceiver.kt @@ -13,21 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package net.micode.notes.ui -package net.micode.notes.gtask.exception; +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent -public class NetworkFailureException extends Exception { - private static final long serialVersionUID = 2107610287180234136L; - - public NetworkFailureException() { - super(); - } - - public NetworkFailureException(String paramString) { - super(paramString); - } - - public NetworkFailureException(String paramString, Throwable paramThrowable) { - super(paramString, paramThrowable); +class AlarmReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + intent.setClass(context, AlarmAlertActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(intent) } } diff --git a/app/src/main/kotlin/net/micode/notes/ui/AppWidgetAttribute.kt b/app/src/main/kotlin/net/micode/notes/ui/AppWidgetAttribute.kt new file mode 100644 index 00000000..e617cf49 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/AppWidgetAttribute.kt @@ -0,0 +1,6 @@ +package net.micode.notes.ui + +data class AppWidgetAttribute( + var widgetId: Int = 0, + var widgetType: Int = 0 +) diff --git a/app/src/main/kotlin/net/micode/notes/ui/NoteEditActivity.kt b/app/src/main/kotlin/net/micode/notes/ui/NoteEditActivity.kt new file mode 100644 index 00000000..bfbd80e2 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/NoteEditActivity.kt @@ -0,0 +1,490 @@ +package net.micode.notes.ui + +import android.app.AlarmManager +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.content.ActivityNotFoundException +import android.content.Intent +import android.content.SharedPreferences +import android.os.Bundle +import android.util.Log +import android.util.TypedValue +import android.view.WindowManager +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.OnBackPressedCallback +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.core.content.edit +import androidx.core.view.WindowCompat +import androidx.lifecycle.ViewModelProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import net.micode.notes.R +import net.micode.notes.data.Notes +import net.micode.notes.data.Notes.TextNote +import net.micode.notes.data.NotesRepository +import net.micode.notes.model.WorkingNote +import net.micode.notes.tool.NotesPreferences +import net.micode.notes.tool.PendingIntentCompat +import net.micode.notes.tool.ResourceParser +import net.micode.notes.tool.defaultPreferences +import net.micode.notes.widget.NoteWidgetUpdater + +class NoteEditActivity : ComponentActivity() { + private lateinit var workingNote: WorkingNote + private lateinit var sharedPrefs: SharedPreferences + private var uiState by mutableStateOf(NoteEditUiState()) + private var deleteDialogVisible by mutableStateOf(false) + private var reminderDialogState by mutableStateOf(null) + + private val noteEditViewModel: NoteEditViewModel by lazy { + ViewModelProvider(this)[NoteEditViewModel::class.java] + } + private val notesRepository: NotesRepository by lazy { + NotesRepository(applicationContext) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + sharedPrefs = defaultPreferences() + val initialIntent = if (savedInstanceState?.containsKey(Intent.EXTRA_UID) == true) { + Intent(Intent.ACTION_VIEW).apply { + putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)) + } + } else { + intent + } + + if (!initActivityState(initialIntent)) { + finish() + return + } + + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + saveNote() + finish() + } + }) + + setContent { + MaterialTheme { + NoteEditScreen( + state = uiState, + deleteDialogVisible = deleteDialogVisible, + reminderDialogState = reminderDialogState, + onBack = { + saveNote() + finish() + }, + onContentChange = { uiState = uiState.copy(content = it) }, + onNewNote = { createNewNote() }, + onDelete = { requestDeleteCurrentNote() }, + onDismissDeleteDialog = { deleteDialogVisible = false }, + onConfirmDelete = { confirmDeleteCurrentNote() }, + onToggleChecklist = { toggleChecklistMode() }, + onShare = { shareCurrentNote() }, + onExportText = { exportCurrentNoteAsTxt() }, + onSaveLongImage = { saveCurrentNoteAsLongImage() }, + onSendToDesktop = { sendToDesktop() }, + onSetReminder = { + reminderDialogState = ReminderDialogUiState( + initialDate = uiState.alertDate.takeIf { it > 0L } + ?: System.currentTimeMillis() + ) + }, + onDismissReminderDialog = { reminderDialogState = null }, + onConfirmReminder = { date -> + reminderDialogState = null + workingNote.setAlertDate(date) + updateAlarm(date, true) + uiState = uiState.copy(alertDate = date) + }, + onClearReminder = { clearReminder() }, + onSelectBackground = { bgColorId -> + workingNote.bgColorId = bgColorId + syncUiStateFromWorkingNote(uiState.content) + }, + onSelectFontSize = { fontSizeId -> + sharedPrefs.edit { putInt(PREFERENCE_FONT_SIZE, fontSizeId) } + uiState = uiState.copy(fontSizeId = fontSizeId) + } + ) + } + } + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + if (initActivityState(intent)) { + syncUiStateFromWorkingNote() + } + } + + override fun onResume() { + super.onResume() + syncUiStateFromWorkingNote(uiState.content) + } + + override fun onPause() { + super.onPause() + saveNote() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + if (!workingNote.existInDatabase()) { + saveNote() + } + outState.putLong(Intent.EXTRA_UID, workingNote.noteId) + } + + private fun initActivityState(intent: Intent): Boolean { + @Suppress("DEPRECATION") + val note = if (intent.action == Intent.ACTION_VIEW) { + var noteId = intent.getLongExtra(Intent.EXTRA_UID, 0) + + val visible = runBlocking(Dispatchers.IO) { + notesRepository.isVisibleNote(noteId, Notes.TYPE_NOTE) + } + if (!visible) { + startActivity(Intent(this, NotesListActivity::class.java)) + showToast(R.string.error_note_not_exist) + return false + } + + window.setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN or + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + ) + loadWorkingNote(noteId) ?: return false + } else if (intent.action == Intent.ACTION_INSERT_OR_EDIT) { + val folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0) + val widgetId = intent.getIntExtra( + Notes.INTENT_EXTRA_WIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID + ) + val widgetType = intent.getIntExtra( + Notes.INTENT_EXTRA_WIDGET_TYPE, + Notes.TYPE_WIDGET_INVALIDE + ) + val bgResId = intent.getIntExtra( + Notes.INTENT_EXTRA_BACKGROUND_ID, + ResourceParser.getDefaultBgId(this) + ) + + val phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER) + val callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0) + val createdNote = if (callDate != 0L && phoneNumber != null) { + val noteId = runBlocking(Dispatchers.IO) { + notesRepository.getNoteIdByPhoneNumberAndCallDate(phoneNumber, callDate) + } + if (noteId > 0) { + loadWorkingNote(noteId) ?: return false + } else { + WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId).apply { + convertToCallNote(phoneNumber, callDate) + } + } + } else { + WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId) + } + + window.setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE or + WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE + ) + createdNote + } else { + Log.e(TAG, "Intent not specified action, should not support") + return false + } + + workingNote = note + deleteDialogVisible = false + reminderDialogState = null + syncUiStateFromWorkingNote() + return true + } + + private fun loadWorkingNote(noteId: Long): WorkingNote? { + return runCatching { WorkingNote.load(this, noteId) } + .onFailure { error -> + Log.e(TAG, "load note failed with note id $noteId", error) + } + .getOrNull() + } + + private fun syncUiStateFromWorkingNote(contentOverride: String? = null) { + val fontSizeId = sharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE) + .takeIf { it < 4 } + ?: ResourceParser.BG_DEFAULT_FONT_SIZE + + uiState = NoteEditUiState( + content = contentOverride ?: workingNote.content.orEmpty(), + bgColorId = workingNote.bgColorId, + bgResId = workingNote.bgColorResId, + titleBgResId = workingNote.titleBgResId, + fontSizeId = fontSizeId, + modifiedDate = if (workingNote.modifiedDate > 0L) { + workingNote.modifiedDate + } else { + System.currentTimeMillis() + }, + alertDate = workingNote.alertDate, + isChecklistMode = workingNote.checkListMode == TextNote.MODE_CHECK_LIST + ) + } + + private fun toggleChecklistMode() { + val enableChecklist = workingNote.checkListMode != TextNote.MODE_CHECK_LIST + val updatedContent = if (enableChecklist) { + toChecklistText(uiState.content) + } else { + fromChecklistText(uiState.content) + } + workingNote.checkListMode = if (enableChecklist) TextNote.MODE_CHECK_LIST else 0 + uiState = uiState.copy( + content = updatedContent, + isChecklistMode = enableChecklist + ) + } + + private fun toChecklistText(text: String): String { + val normalized = fromChecklistText(text) + return normalized.lineSequence() + .filter { it.isNotBlank() } + .joinToString("\n") { "$TAG_UNCHECKED ${it.trim()}" } + } + + private fun fromChecklistText(text: String): String { + return text.lineSequence() + .joinToString("\n") { line -> + line.removePrefix("$TAG_CHECKED ") + .removePrefix("$TAG_UNCHECKED ") + } + .trimEnd() + } + + private fun deleteCurrentNote() { + if (workingNote.existInDatabase()) { + val id = workingNote.noteId + val deleted = if (id != Notes.ID_ROOT_FOLDER.toLong()) { + runBlocking(Dispatchers.IO) { + notesRepository.deleteNotes(setOf(id)) + } + } else { + false + } + if (!deleted) { + Log.e(TAG, "Delete note failed") + } + } + workingNote.markDeleted() + updateWidgetIfNeeded() + } + + private fun requestDeleteCurrentNote() { + if (NotesPreferences.isDeleteConfirmationEnabled(this)) { + deleteDialogVisible = true + } else { + confirmDeleteCurrentNote() + } + } + + private fun confirmDeleteCurrentNote() { + deleteDialogVisible = false + setResult(RESULT_OK) + deleteCurrentNote() + finish() + } + + private fun clearReminder() { + reminderDialogState = null + workingNote.setAlertDate(0) + updateAlarm(0, false) + uiState = uiState.copy(alertDate = 0L) + } + + private fun updateAlarm(date: Long, set: Boolean) { + if (!workingNote.existInDatabase()) { + saveNote() + } + if (workingNote.noteId <= 0) { + showToast(R.string.error_note_empty_for_clock) + return + } + + val intent = Intent(this, AlarmReceiver::class.java).apply { + putExtra(Intent.EXTRA_UID, workingNote.noteId) + } + val pendingIntent = PendingIntent.getBroadcast( + this, + 0, + intent, + PendingIntentCompat.immutableFlag() + ) + val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager + if (!set) { + alarmManager.cancel(pendingIntent) + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent) + } + } + + private fun createNewNote() { + saveNote() + finish() + startActivity( + Intent(this, NoteEditActivity::class.java).apply { + action = Intent.ACTION_INSERT_OR_EDIT + putExtra(Notes.INTENT_EXTRA_FOLDER_ID, workingNote.folderId) + } + ) + } + + private fun shareCurrentNote() { + val text = currentNoteTextForShare + val intent = Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_TEXT, text) + putExtra(Intent.EXTRA_SUBJECT, getString(R.string.app_name)) + type = "text/plain" + } + try { + startActivity(Intent.createChooser(intent, getString(R.string.share_note_chooser_title))) + } catch (error: ActivityNotFoundException) { + Log.e(TAG, "Share note failed", error) + showToast(R.string.error_no_share_app) + } + } + + private fun exportCurrentNoteAsTxt() { + val noteText = currentNoteTextForExport + if (noteText.isBlank()) { + showToast(R.string.error_note_empty_for_export) + return + } + + noteEditViewModel.exportCurrentNoteAsTxt(applicationContext, noteText) { exported -> + showToast(if (exported) R.string.success_sdcard_export else R.string.error_sdcard_export) + } + } + + private fun saveCurrentNoteAsLongImage() { + val noteText = currentNoteTextForLongImage + if (noteText.isBlank()) { + showToast(R.string.error_note_empty_for_export) + return + } + + noteEditViewModel.saveCurrentNoteAsLongImage( + applicationContext, + noteText, + currentEditorTextSize(), + workingNote.bgColorResId, + resources.displayMetrics.widthPixels + ) { saved -> + showToast( + if (saved) R.string.success_long_image_saved else R.string.error_long_image_save_failed + ) + } + } + + @Suppress("DEPRECATION") + private fun sendToDesktop() { + if (!workingNote.existInDatabase()) { + saveNote() + } + + if (workingNote.noteId <= 0) { + showToast(R.string.error_note_empty_for_send_to_desktop) + return + } + + val sender = Intent() + val shortcutIntent = Intent(this, NoteEditActivity::class.java).apply { + action = Intent.ACTION_VIEW + putExtra(Intent.EXTRA_UID, workingNote.noteId) + } + sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent) + sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, makeShortcutIconTitle(uiState.content)) + sender.putExtra( + Intent.EXTRA_SHORTCUT_ICON_RESOURCE, + Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app) + ) + sender.putExtra("duplicate", true) + sender.action = "com.android.launcher.action.INSTALL_SHORTCUT" + sendBroadcast(sender) + showToast(R.string.info_note_enter_desktop) + } + + private fun saveNote(): Boolean { + workingNote.setWorkingText(uiState.content) + val saved = workingNote.saveNote() + if (saved) { + setResult(RESULT_OK) + uiState = uiState.copy(modifiedDate = workingNote.modifiedDate) + updateWidgetIfNeeded() + } + return saved + } + + private fun updateWidgetIfNeeded() { + if (workingNote.widgetId == AppWidgetManager.INVALID_APPWIDGET_ID || + workingNote.widgetType == Notes.TYPE_WIDGET_INVALIDE + ) { + return + } + NoteWidgetUpdater.updateWidget(this, workingNote.widgetId, workingNote.widgetType) + } + + private fun currentEditorTextSize(): Float { + val spValue = when (uiState.fontSizeId) { + ResourceParser.TEXT_SMALL -> 15 + ResourceParser.TEXT_MEDIUM -> 18 + ResourceParser.TEXT_LARGE -> 22 + ResourceParser.TEXT_SUPER -> 26 + else -> 18 + } + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, + spValue.toFloat(), + resources.displayMetrics + ) + } + + private val currentNoteTextForExport: String + get() = fromChecklistText(uiState.content) + + private val currentNoteTextForShare: String + get() = uiState.content + + private val currentNoteTextForLongImage: String + get() = uiState.content + + private fun makeShortcutIconTitle(content: String): String { + val title = content.replace(TAG_CHECKED, "").replace(TAG_UNCHECKED, "") + return if (title.length > SHORTCUT_ICON_TITLE_MAX_LEN) { + title.substring(0, SHORTCUT_ICON_TITLE_MAX_LEN) + } else { + title + } + } + + private fun showToast(resId: Int, duration: Int = Toast.LENGTH_SHORT) { + Toast.makeText(this, resId, duration).show() + } + + companion object { + private const val TAG = "NoteEditActivity" + private const val PREFERENCE_FONT_SIZE = "pref_font_size" + private const val SHORTCUT_ICON_TITLE_MAX_LEN = 10 + val TAG_CHECKED: String = '\u221A'.toString() + val TAG_UNCHECKED: String = '\u25A1'.toString() + } +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/NoteEditScreen.kt b/app/src/main/kotlin/net/micode/notes/ui/NoteEditScreen.kt new file mode 100644 index 00000000..36c47335 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/NoteEditScreen.kt @@ -0,0 +1,799 @@ +package net.micode.notes.ui + +import android.widget.ImageView +import android.text.format.DateUtils +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.viewinterop.AndroidView +import net.micode.notes.R +import net.micode.notes.tool.ResourceParser +import java.text.DateFormat +import java.util.Calendar +import java.util.Date +import android.text.format.DateFormat as AndroidDateFormat + +data class NoteEditUiState( + val content: String = "", + val bgColorId: Int = ResourceParser.BG_DEFAULT_COLOR, + val bgResId: Int = 0, + val titleBgResId: Int = 0, + val fontSizeId: Int = ResourceParser.BG_DEFAULT_FONT_SIZE, + val modifiedDate: Long = 0L, + val alertDate: Long = 0L, + val isChecklistMode: Boolean = false +) + +data class ReminderDialogUiState( + val initialDate: Long = System.currentTimeMillis() +) + +private val HeaderActionColor = Color(0xFF7A6A43) +private val HeaderTitleColor = Color(0xFF231C12) +private val HeaderSubtitleColor = Color(0xAA3D3427) +private val EditorControlSurfaceColor = Color(0xF0E5D6A8) +private val EditorTextColor = Color(0xFF2A261F) +private val EditorHintColor = Color(0x88544E43) + +@Composable +fun NoteEditScreen( + state: NoteEditUiState, + deleteDialogVisible: Boolean, + reminderDialogState: ReminderDialogUiState?, + onBack: () -> Unit, + onContentChange: (String) -> Unit, + onNewNote: () -> Unit, + onDelete: () -> Unit, + onDismissDeleteDialog: () -> Unit, + onConfirmDelete: () -> Unit, + onToggleChecklist: () -> Unit, + onShare: () -> Unit, + onExportText: () -> Unit, + onSaveLongImage: () -> Unit, + onSendToDesktop: () -> Unit, + onSetReminder: () -> Unit, + onDismissReminderDialog: () -> Unit, + onConfirmReminder: (Long) -> Unit, + onClearReminder: () -> Unit, + onSelectBackground: (Int) -> Unit, + onSelectFontSize: (Int) -> Unit +) { + Box(modifier = Modifier.fillMaxSize()) { + if (state.bgResId != 0) { + ResourceDrawableBackground( + resId = state.bgResId, + modifier = Modifier.fillMaxSize() + ) + } else { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFFFF3BF)) + ) + } + + Column(modifier = Modifier.fillMaxSize()) { + NoteEditTopBar( + state = state, + onBack = onBack, + onNewNote = onNewNote, + onDelete = onDelete, + onToggleChecklist = onToggleChecklist, + onShare = onShare, + onExportText = onExportText, + onSaveLongImage = onSaveLongImage, + onSendToDesktop = onSendToDesktop, + onSetReminder = onSetReminder, + onClearReminder = onClearReminder + ) + + if (state.alertDate > 0L) { + Surface( + color = Color(0xCCFFF7E3), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + shape = RoundedCornerShape(18.dp) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + LegacyAssetIcon( + resId = R.drawable.title_alert, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + Spacer(modifier = Modifier.size(10.dp)) + Text( + text = DateUtils.getRelativeTimeSpanString( + state.alertDate, + System.currentTimeMillis(), + DateUtils.MINUTE_IN_MILLIS + ).toString(), + modifier = Modifier.weight(1f), + color = HeaderSubtitleColor + ) + RemoveReminderAction(onClick = onClearReminder) + } + } + } + + Column( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .padding(horizontal = 18.dp, vertical = 6.dp) + ) { + LegacyAssetIcon( + resId = R.drawable.bg_color_btn_mask, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .height(7.dp) + ) + Box( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + ) { + val scrollState = rememberScrollState() + BasicTextField( + value = state.content, + onValueChange = onContentChange, + textStyle = TextStyle( + color = EditorTextColor, + fontSize = fontSizeFor(state.fontSizeId), + lineHeight = (fontSizeFor(state.fontSizeId).value * 1.5f).sp + ), + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 18.dp, vertical = 14.dp) + .verticalScroll(scrollState), + decorationBox = { innerTextField -> + if (state.content.isEmpty()) { + Text( + text = if (state.isChecklistMode) { + stringResource(R.string.checklist_placeholder) + } else { + stringResource(R.string.note_editor_placeholder) + }, + color = EditorHintColor, + fontSize = fontSizeFor(state.fontSizeId) + ) + } + innerTextField() + } + ) + } + LegacyAssetIcon( + resId = R.drawable.bg_color_btn_mask, + contentDescription = null, + modifier = Modifier + .fillMaxWidth() + .height(7.dp) + ) + } + + EditorControls( + state = state, + onSelectBackground = onSelectBackground, + onSelectFontSize = onSelectFontSize + ) + } + + if (deleteDialogVisible) { + AlertDialog( + onDismissRequest = onDismissDeleteDialog, + title = { Text(text = stringResource(R.string.alert_title_delete)) }, + text = { Text(text = stringResource(R.string.alert_message_delete_note)) }, + confirmButton = { + TextButton(onClick = onConfirmDelete) { + Text(text = stringResource(android.R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = onDismissDeleteDialog) { + Text(text = stringResource(android.R.string.cancel)) + } + } + ) + } + + reminderDialogState?.let { dialogState -> + ReminderDialog( + state = dialogState, + onDismiss = onDismissReminderDialog, + onConfirm = onConfirmReminder + ) + } + } +} + +@Composable +private fun NoteEditTopBar( + state: NoteEditUiState, + onBack: () -> Unit, + onNewNote: () -> Unit, + onDelete: () -> Unit, + onToggleChecklist: () -> Unit, + onShare: () -> Unit, + onExportText: () -> Unit, + onSaveLongImage: () -> Unit, + onSendToDesktop: () -> Unit, + onSetReminder: () -> Unit, + onClearReminder: () -> Unit +) { + var expanded by remember { mutableStateOf(false) } + Box(modifier = Modifier.fillMaxWidth()) { + if (state.titleBgResId != 0) { + ResourceDrawableBackground( + resId = state.titleBgResId, + modifier = Modifier.matchParentSize() + ) + } else { + Box( + modifier = Modifier + .matchParentSize() + .background(Color(0xDDECDCB6)) + ) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .statusBarsPadding() + .padding(horizontal = 18.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + HeaderActionText( + text = stringResource(R.string.action_back), + onClick = onBack + ) + Column(modifier = Modifier.weight(1f)) { + Text( + text = stringResource(R.string.app_name), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + color = HeaderTitleColor + ) + Text( + text = DateFormat.getDateTimeInstance( + DateFormat.SHORT, + DateFormat.SHORT + ).format(Date(state.modifiedDate)), + style = MaterialTheme.typography.bodyMedium, + color = HeaderSubtitleColor + ) + } + Box { + HeaderActionText( + text = stringResource(R.string.action_more), + onClick = { expanded = true } + ) + DropdownMenu( + expanded = expanded, + onDismissRequest = { expanded = false } + ) { + DropdownMenuItem( + text = { Text(text = stringResource(R.string.notelist_menu_new)) }, + onClick = { + expanded = false + onNewNote() + } + ) + DropdownMenuItem( + leadingIcon = { + LegacyAssetIcon( + resId = R.drawable.menu_delete, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + }, + text = { Text(text = stringResource(R.string.menu_delete)) }, + onClick = { + expanded = false + onDelete() + } + ) + DropdownMenuItem( + text = { + Text( + text = stringResource( + if (state.isChecklistMode) { + R.string.menu_normal_mode + } else { + R.string.menu_list_mode + } + ) + ) + }, + onClick = { + expanded = false + onToggleChecklist() + } + ) + DropdownMenuItem( + text = { Text(text = stringResource(R.string.menu_share)) }, + onClick = { + expanded = false + onShare() + } + ) + DropdownMenuItem( + text = { Text(text = stringResource(R.string.menu_export_as_txt)) }, + onClick = { + expanded = false + onExportText() + } + ) + DropdownMenuItem( + text = { Text(text = stringResource(R.string.menu_generate_long_image)) }, + onClick = { + expanded = false + onSaveLongImage() + } + ) + DropdownMenuItem( + leadingIcon = { + LegacyAssetIcon( + resId = R.drawable.menu_move, + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + }, + text = { Text(text = stringResource(R.string.menu_send_to_desktop)) }, + onClick = { + expanded = false + onSendToDesktop() + } + ) + DropdownMenuItem( + leadingIcon = { + LegacyAssetIcon( + resId = R.drawable.clock, + contentDescription = null, + modifier = Modifier.size(width = 18.dp, height = 22.dp) + ) + }, + text = { + Text( + text = stringResource( + if (state.alertDate > 0L) { + R.string.menu_remove_remind + } else { + R.string.menu_alert + } + ) + ) + }, + onClick = { + expanded = false + if (state.alertDate > 0L) { + onClearReminder() + } else { + onSetReminder() + } + } + ) + } + } + } + } +} + +@Composable +private fun HeaderActionText( + text: String, + onClick: () -> Unit +) { + Box( + modifier = Modifier + .clip(RoundedCornerShape(14.dp)) + .clickable(onClick = onClick) + .padding(horizontal = 10.dp, vertical = 8.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = text, + style = MaterialTheme.typography.titleMedium, + color = HeaderActionColor, + fontWeight = FontWeight.SemiBold + ) + } +} + +@Composable +private fun RemoveReminderAction(onClick: () -> Unit) { + Row( + modifier = Modifier + .clip(RoundedCornerShape(14.dp)) + .clickable(onClick = onClick) + .padding(horizontal = 8.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + LegacyAssetIcon( + resId = R.drawable.delete, + contentDescription = null, + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.size(6.dp)) + Text( + text = stringResource(R.string.menu_remove_remind), + color = HeaderActionColor, + style = MaterialTheme.typography.labelLarge, + fontWeight = FontWeight.Medium + ) + } +} + +@Composable +private fun ReminderDialog( + state: ReminderDialogUiState, + onDismiss: () -> Unit, + onConfirm: (Long) -> Unit +) { + val context = LocalContext.current + val initialCalendar = remember(state.initialDate) { + Calendar.getInstance().apply { + timeInMillis = state.initialDate + } + } + var year by remember(state.initialDate) { + mutableIntStateOf(initialCalendar.get(Calendar.YEAR)) + } + var month by remember(state.initialDate) { + mutableIntStateOf(initialCalendar.get(Calendar.MONTH)) + } + var dayOfMonth by remember(state.initialDate) { + mutableIntStateOf(initialCalendar.get(Calendar.DAY_OF_MONTH)) + } + var selectedHourOfDay by remember(state.initialDate) { + mutableIntStateOf(initialCalendar.get(Calendar.HOUR_OF_DAY)) + } + var selectedMinute by remember(state.initialDate) { + mutableIntStateOf(initialCalendar.get(Calendar.MINUTE)) + } + val is24Hour = remember(context) { AndroidDateFormat.is24HourFormat(context) } + + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(text = stringResource(R.string.menu_alert)) }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { + AndroidView( + factory = { viewContext -> + android.widget.DatePicker(viewContext).apply { + init(year, month, dayOfMonth) { _, selectedYear, selectedMonth, selectedDay -> + year = selectedYear + month = selectedMonth + dayOfMonth = selectedDay + } + } + }, + update = { datePicker -> + datePicker.updateDate(year, month, dayOfMonth) + }, + modifier = Modifier.fillMaxWidth() + ) + AndroidView( + factory = { viewContext -> + android.widget.TimePicker(viewContext).apply { + setIs24HourView(is24Hour) + hour = selectedHourOfDay + minute = selectedMinute + setOnTimeChangedListener { _, newHour, newMinute -> + selectedHourOfDay = newHour + selectedMinute = newMinute + } + } + }, + update = { timePicker -> + timePicker.setIs24HourView(is24Hour) + if (timePicker.hour != selectedHourOfDay) { + timePicker.hour = selectedHourOfDay + } + if (timePicker.minute != selectedMinute) { + timePicker.minute = selectedMinute + } + }, + modifier = Modifier.fillMaxWidth() + ) + } + }, + confirmButton = { + TextButton( + onClick = { + val calendar = Calendar.getInstance().apply { + set(Calendar.YEAR, year) + set(Calendar.MONTH, month) + set(Calendar.DAY_OF_MONTH, dayOfMonth) + set(Calendar.HOUR_OF_DAY, selectedHourOfDay) + set(Calendar.MINUTE, selectedMinute) + set(Calendar.SECOND, 0) + set(Calendar.MILLISECOND, 0) + } + onConfirm(calendar.timeInMillis) + } + ) { + Text(text = stringResource(android.R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(text = stringResource(android.R.string.cancel)) + } + } + ) +} + +@Composable +private fun EditorControls( + state: NoteEditUiState, + onSelectBackground: (Int) -> Unit, + onSelectFontSize: (Int) -> Unit +) { + Surface( + modifier = Modifier.fillMaxWidth(), + color = EditorControlSurfaceColor, + shape = RoundedCornerShape(topStart = 28.dp, topEnd = 28.dp) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(horizontal = 14.dp, vertical = 10.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + Box( + modifier = Modifier + .size(width = 38.dp, height = 44.dp), + contentAlignment = Alignment.Center + ) { + LegacyAssetIcon( + resId = R.drawable.bg_btn_set_color, + contentDescription = null, + modifier = Modifier.size(26.dp) + ) + } + + Box( + modifier = Modifier + .weight(1f) + .height(62.dp) + ) { + LegacyAssetIcon( + resId = R.drawable.note_edit_color_selector_panel, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + fillBounds = true + ) + Row( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 14.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + listOf( + ResourceParser.YELLOW, + ResourceParser.BLUE, + ResourceParser.WHITE, + ResourceParser.GREEN, + ResourceParser.RED + ).forEach { bgId -> + Box( + modifier = Modifier + .weight(1f) + .height(44.dp) + .clip(RoundedCornerShape(8.dp)) + .background(colorForBackground(bgId)) + .border( + width = if (state.bgColorId == bgId) 2.dp else 1.dp, + color = if (state.bgColorId == bgId) { + Color(0xFF6A4A1E) + } else { + Color(0x66443A2D) + }, + shape = RoundedCornerShape(8.dp) + ) + .clickable { onSelectBackground(bgId) } + ) { + if (state.bgColorId == bgId) { + LegacyAssetIcon( + resId = R.drawable.selected, + contentDescription = null, + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(end = 2.dp) + .size(width = 16.dp, height = 21.dp) + ) + } + } + } + } + } + } + + Box( + modifier = Modifier + .fillMaxWidth() + .height(72.dp) + ) { + LegacyAssetIcon( + resId = R.drawable.font_size_selector_bg, + contentDescription = null, + modifier = Modifier.fillMaxSize(), + fillBounds = true + ) + Row( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 8.dp, vertical = 8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + fontOptions().forEach { option -> + Box( + modifier = Modifier + .weight(1f) + .fillMaxHeight() + .clip(RoundedCornerShape(14.dp)) + .clickable { onSelectFontSize(option.fontId) } + ) { + Column( + modifier = Modifier + .align(Alignment.Center) + .padding(top = 2.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + LegacyAssetIcon( + resId = option.drawableRes, + contentDescription = stringResource(option.labelRes), + modifier = Modifier.size(width = 24.dp, height = 28.dp) + ) + Text( + text = stringResource(option.labelRes), + fontSize = 10.sp, + lineHeight = 10.sp, + textAlign = TextAlign.Center, + color = Color(0xFF5A5348) + ) + } + if (state.fontSizeId == option.fontId) { + LegacyAssetIcon( + resId = R.drawable.selected, + contentDescription = null, + modifier = Modifier + .align(Alignment.BottomEnd) + .padding(end = 2.dp) + .size(width = 16.dp, height = 21.dp) + ) + } + } + } + } + } + } + } +} + +@Composable +private fun fontOptions(): List { + return listOf( + FontOption(ResourceParser.TEXT_SMALL, R.drawable.font_small, R.string.menu_font_small), + FontOption(ResourceParser.TEXT_MEDIUM, R.drawable.font_normal, R.string.menu_font_normal), + FontOption(ResourceParser.TEXT_LARGE, R.drawable.font_large, R.string.menu_font_large), + FontOption(ResourceParser.TEXT_SUPER, R.drawable.font_super, R.string.menu_font_super) + ) +} + +private data class FontOption( + val fontId: Int, + @param:DrawableRes val drawableRes: Int, + @param:StringRes val labelRes: Int +) + +@Composable +private fun LegacyAssetIcon( + @DrawableRes resId: Int, + contentDescription: String?, + modifier: Modifier = Modifier, + fillBounds: Boolean = false +) { + AndroidView( + modifier = modifier, + factory = { context -> + ImageView(context).apply { + scaleType = if (fillBounds) { + ImageView.ScaleType.FIT_XY + } else { + ImageView.ScaleType.FIT_CENTER + } + adjustViewBounds = !fillBounds + setImageResource(resId) + this.contentDescription = contentDescription + } + }, + update = { view -> + view.scaleType = if (fillBounds) { + ImageView.ScaleType.FIT_XY + } else { + ImageView.ScaleType.FIT_CENTER + } + view.adjustViewBounds = !fillBounds + view.setImageResource(resId) + view.contentDescription = contentDescription + } + ) +} + +private fun fontSizeFor(fontSizeId: Int) = when (fontSizeId) { + ResourceParser.TEXT_SMALL -> 15.sp + ResourceParser.TEXT_MEDIUM -> 18.sp + ResourceParser.TEXT_LARGE -> 22.sp + ResourceParser.TEXT_SUPER -> 26.sp + else -> 18.sp +} + +private fun colorForBackground(bgColorId: Int) = when (bgColorId) { + ResourceParser.YELLOW -> Color(0xFFEECF68) + ResourceParser.BLUE -> Color(0xFF8DB6E9) + ResourceParser.WHITE -> Color(0xFFF5F2E8) + ResourceParser.GREEN -> Color(0xFFA1C88C) + ResourceParser.RED -> Color(0xFFE59882) + else -> Color(0xFFEECF68) +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/NoteEditViewModel.kt b/app/src/main/kotlin/net/micode/notes/ui/NoteEditViewModel.kt new file mode 100644 index 00000000..1c12b203 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/NoteEditViewModel.kt @@ -0,0 +1,352 @@ +package net.micode.notes.ui + +import android.content.ContentValues +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.os.Build +import android.os.Environment +import android.provider.MediaStore +import android.text.Layout +import android.text.StaticLayout +import android.text.TextPaint +import android.text.format.DateFormat +import android.util.Log +import android.util.TypedValue +import androidx.annotation.RequiresApi +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.createBitmap +import androidx.core.graphics.withTranslation +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import kotlin.math.max + +class NoteEditViewModel : ViewModel() { + fun exportCurrentNoteAsTxt( + context: Context, + noteText: String, + onComplete: (Boolean) -> Unit + ) { + val appContext = context.applicationContext + viewModelScope.launch(Dispatchers.IO) { + val exported = writeNoteText(appContext, noteText) + withContext(Dispatchers.Main) { + onComplete(exported) + } + } + } + + fun saveCurrentNoteAsLongImage( + context: Context, + noteText: String, + textSize: Float, + backgroundResId: Int, + imageWidth: Int, + onComplete: (Boolean) -> Unit + ) { + val appContext = context.applicationContext + viewModelScope.launch(Dispatchers.IO) { + val bitmap = createLongImageBitmap( + appContext, + noteText, + textSize, + backgroundResId, + imageWidth + ) + val saved = saveLongImageBitmap(appContext, bitmap) + withContext(Dispatchers.Main) { + onComplete(saved) + } + } + } + + private fun createLongImageBitmap( + context: Context, + noteText: String, + textSize: Float, + backgroundResId: Int, + imageWidth: Int + ): Bitmap? { + return try { + val content = noteText.ifEmpty { " " } + val horizontalPadding = dpToPx(context, LONG_IMAGE_HORIZONTAL_PADDING_DP) + val verticalPadding = dpToPx(context, LONG_IMAGE_VERTICAL_PADDING_DP) + val contentWidth = max(imageWidth - horizontalPadding * 2, dpToPx(context, 160)) + + val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + color = Color.rgb(51, 51, 51) + this.textSize = if (textSize > 0) textSize else spToPx(context, 18) + } + + val layout = createTextLayout(context, content, textPaint, contentWidth) + val imageHeight = max( + layout.height + verticalPadding * 2, + dpToPx(context, LONG_IMAGE_MIN_HEIGHT_DP) + ) + + createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888).apply { + val canvas = Canvas(this) + drawLongImageBackground(context, canvas, backgroundResId, imageWidth, imageHeight) + canvas.withTranslation(horizontalPadding.toFloat(), verticalPadding.toFloat()) { + layout.draw(this) + } + } + } catch (error: OutOfMemoryError) { + Log.e(TAG, "Create long image bitmap failed", error) + null + } + } + + private fun createTextLayout( + context: Context, + content: String, + textPaint: TextPaint, + contentWidth: Int + ): StaticLayout { + val spacingAdd = dpToPx(context, 4).toFloat() + return StaticLayout.Builder + .obtain(content, 0, content.length, textPaint, contentWidth) + .setAlignment(Layout.Alignment.ALIGN_NORMAL) + .setLineSpacing(spacingAdd, 1.4f) + .setIncludePad(false) + .build() + } + + private fun drawLongImageBackground( + context: Context, + canvas: Canvas, + backgroundResId: Int, + width: Int, + height: Int + ) { + try { + ResourcesCompat.getDrawable(context.resources, backgroundResId, context.theme)?.let { background -> + background.setBounds(0, 0, width, height) + background.draw(canvas) + return + } + } catch (error: Exception) { + Log.e(TAG, "Draw long image background failed", error) + } + canvas.drawColor(Color.WHITE) + } + + private fun saveLongImageBitmap(context: Context, bitmap: Bitmap?): Boolean { + bitmap ?: return false + + return try { + val displayName = + "note_long_image_${DateFormat.format("yyyyMMdd_HHmmss", System.currentTimeMillis())}.png" + val saved = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + saveBitmapToMediaStore(context, bitmap, displayName) + } else { + saveBitmapToScopedFile( + context, + bitmap, + Environment.DIRECTORY_PICTURES, + "Notes", + displayName + ) + } + saved + } finally { + bitmap.recycle() + } + } + + private fun writeNoteText(context: Context, noteText: String): Boolean { + val displayName = + "note_${DateFormat.format("yyyyMMdd_HHmmss", System.currentTimeMillis())}.txt" + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + saveTextToMediaStore(context, noteText, displayName) + } else { + saveTextToScopedFile( + context, + noteText, + Environment.DIRECTORY_DOWNLOADS, + "Notes", + displayName + ) + } + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun saveTextToMediaStore( + context: Context, + noteText: String, + displayName: String + ): Boolean { + val resolver = context.contentResolver + val values = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) + put(MediaStore.MediaColumns.MIME_TYPE, "text/plain") + put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DOWNLOADS}/Notes") + put(MediaStore.MediaColumns.IS_PENDING, 1) + } + val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values) ?: return false + + return try { + resolver.openOutputStream(uri)?.bufferedWriter(Charsets.UTF_8).use { writer -> + if (writer == null) { + resolver.delete(uri, null, null) + return false + } + writer.write(noteText) + } + markMediaStoreItemReady(context, uri) + true + } catch (error: IOException) { + Log.e(TAG, "Export note as txt failed", error) + resolver.delete(uri, null, null) + false + } + } + + @RequiresApi(Build.VERSION_CODES.Q) + private fun saveBitmapToMediaStore( + context: Context, + bitmap: Bitmap, + displayName: String + ): Boolean { + val resolver = context.contentResolver + val values = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, displayName) + put(MediaStore.MediaColumns.MIME_TYPE, "image/png") + put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_PICTURES}/Notes") + put(MediaStore.MediaColumns.IS_PENDING, 1) + } + val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) ?: return false + + return try { + resolver.openOutputStream(uri).use { stream -> + if (stream == null || !bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)) { + resolver.delete(uri, null, null) + return false + } + } + markMediaStoreItemReady(context, uri) + true + } catch (error: IOException) { + Log.e(TAG, "Save long image bitmap failed", error) + resolver.delete(uri, null, null) + false + } + } + + private fun markMediaStoreItemReady(context: Context, uri: android.net.Uri) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return + } + + context.contentResolver.update( + uri, + ContentValues().apply { + put(MediaStore.MediaColumns.IS_PENDING, 0) + }, + null, + null + ) + } + + private fun saveTextToScopedFile( + context: Context, + noteText: String, + type: String, + childDir: String, + displayName: String + ): Boolean { + val exportFile = createScopedExportFile(context, type, childDir, displayName) ?: return false + return try { + exportFile.bufferedWriter(Charsets.UTF_8).use { writer -> + writer.write(noteText) + } + true + } catch (error: IOException) { + Log.e(TAG, "Export note as txt failed", error) + exportFile.delete() + false + } + } + + private fun saveBitmapToScopedFile( + context: Context, + bitmap: Bitmap, + type: String, + childDir: String, + displayName: String + ): Boolean { + val exportFile = createScopedExportFile(context, type, childDir, displayName) ?: return false + return try { + FileOutputStream(exportFile).use { output -> + if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)) { + exportFile.delete() + return false + } + output.flush() + } + true + } catch (error: IOException) { + Log.e(TAG, "Save long image bitmap failed", error) + exportFile.delete() + false + } + } + + private fun createScopedExportFile( + context: Context, + type: String, + childDir: String, + displayName: String + ): File? { + val baseDir = context.getExternalFilesDir(type) ?: File(context.filesDir, "exports/$type") + val exportDir = File(baseDir, childDir) + if (!exportDir.exists() && !exportDir.mkdirs()) { + Log.e(TAG, "Create export directory failed: ${exportDir.absolutePath}") + return null + } + + val exportFile = File(exportDir, displayName) + return try { + if (!exportFile.exists() && !exportFile.createNewFile()) { + Log.e(TAG, "Create export file failed: ${exportFile.absolutePath}") + null + } else { + exportFile + } + } catch (error: IOException) { + Log.e(TAG, "Create export file failed", error) + null + } + } + + private fun dpToPx(context: Context, dp: Int): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + dp.toFloat(), + context.resources.displayMetrics + ).toInt() + } + + private fun spToPx(context: Context, sp: Int): Float { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, + sp.toFloat(), + context.resources.displayMetrics + ) + } + + companion object { + private const val TAG = "NoteEditViewModel" + private const val LONG_IMAGE_HORIZONTAL_PADDING_DP = 24 + private const val LONG_IMAGE_VERTICAL_PADDING_DP = 32 + private const val LONG_IMAGE_MIN_HEIGHT_DP = 240 + } +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/NotesListActivity.kt b/app/src/main/kotlin/net/micode/notes/ui/NotesListActivity.kt new file mode 100644 index 00000000..930ddc89 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/NotesListActivity.kt @@ -0,0 +1,268 @@ +package net.micode.notes.ui + +import android.app.AlertDialog +import android.appwidget.AppWidgetManager +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.core.content.edit +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import net.micode.notes.R +import net.micode.notes.data.Notes +import net.micode.notes.model.WorkingNote +import net.micode.notes.tool.NotesPreferences +import net.micode.notes.tool.ResourceParser +import net.micode.notes.tool.defaultPreferences +import net.micode.notes.widget.NoteWidgetUpdater + +class NotesListActivity : ComponentActivity() { + private var folderDialogState by mutableStateOf(null) + + private val noteEditorLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { + notesListViewModel.refresh(this) + } + + private val notesListViewModel: NotesListViewModel by lazy { + ViewModelProvider(this)[NotesListViewModel::class.java] + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setAppInfoFromRawRes() + if (savedInstanceState == null && notesListViewModel.uiState.value == NotesListUiState()) { + restoreLastFolder() + } + + setContent { + MaterialTheme { + val uiState by notesListViewModel.uiState.collectAsStateWithLifecycle() + NotesListScreen( + state = uiState, + folderDialogState = folderDialogState, + onBack = { handleBack() }, + onSearchTextChange = { notesListViewModel.setSearchText(this, it) }, + onItemClick = { item -> handleItemClick(item) }, + onItemLongClick = { item -> notesListViewModel.toggleSelection(item.id) }, + onCreateNote = { createNewNote(uiState.currentFolderId) }, + onCreateFolder = { + folderDialogState = FolderDialogUiState(create = true, name = "") + }, + onDeleteSelection = { confirmDeleteSelected(uiState.selectedIds.size) }, + onClearSelection = { notesListViewModel.clearSelection() }, + onRenameFolder = { + folderDialogState = FolderDialogUiState( + create = false, + name = it.title, + folder = it + ) + }, + onDeleteFolder = { confirmDeleteFolder(it) }, + onOpenSettings = { startPreferenceActivity() }, + onFolderDialogNameChange = { name -> + folderDialogState = folderDialogState?.copy(name = name) + }, + onDismissFolderDialog = { folderDialogState = null }, + onConfirmFolderDialog = { confirmFolderDialog() } + ) + } + } + } + + override fun onResume() { + super.onResume() + notesListViewModel.refresh(this) + } + + private fun handleBack() { + val state = notesListViewModel.uiState.value + when { + state.selectedIds.isNotEmpty() -> notesListViewModel.clearSelection() + !state.isRoot -> { + notesListViewModel.goToRoot(this) + rememberFolder(Notes.ID_ROOT_FOLDER.toLong(), "") + } + else -> finish() + } + } + + private fun handleItemClick(item: NotesListItemUi) { + val selectionActive = notesListViewModel.uiState.value.selectedIds.isNotEmpty() + when { + selectionActive && !item.isFolder -> notesListViewModel.toggleSelection(item.id) + selectionActive -> Unit + item.isFolder -> { + notesListViewModel.openFolder(this, item) + rememberFolder(item.id, item.title) + } + else -> openNote(item.id) + } + } + + private fun createNewNote(folderId: Long) { + val intent = Intent(this, NoteEditActivity::class.java).apply { + action = Intent.ACTION_INSERT_OR_EDIT + putExtra(Notes.INTENT_EXTRA_FOLDER_ID, folderId) + } + noteEditorLauncher.launch(intent) + } + + private fun openNote(noteId: Long) { + val intent = Intent(this, NoteEditActivity::class.java).apply { + action = Intent.ACTION_VIEW + putExtra(Intent.EXTRA_UID, noteId) + } + noteEditorLauncher.launch(intent) + } + + private fun confirmDeleteSelected(selectedCount: Int) { + if (selectedCount == 0) { + Toast.makeText(this, getString(R.string.menu_select_none), Toast.LENGTH_SHORT).show() + return + } + + if (!NotesPreferences.isDeleteConfirmationEnabled(this)) { + deleteSelectedNotes() + return + } + + AlertDialog.Builder(this) + .setTitle(getString(R.string.alert_title_delete)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage( + resources.getQuantityString( + R.plurals.alert_message_delete_notes, + selectedCount, + selectedCount + ) + ) + .setPositiveButton(android.R.string.ok) { _, _ -> deleteSelectedNotes() } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + private fun confirmDeleteFolder(folder: NotesListItemUi) { + if (!NotesPreferences.isDeleteConfirmationEnabled(this)) { + deleteFolder(folder) + return + } + + AlertDialog.Builder(this) + .setTitle(getString(R.string.alert_title_delete)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setMessage(getString(R.string.alert_message_delete_folder)) + .setPositiveButton(android.R.string.ok) { _, _ -> deleteFolder(folder) } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + + private fun deleteSelectedNotes() { + notesListViewModel.deleteSelectedNotes(context = this) { widgets -> + updateWidgets(widgets) + notesListViewModel.refresh(this) + } + } + + private fun deleteFolder(folder: NotesListItemUi) { + notesListViewModel.deleteFolder(context = this, folderId = folder.id) { widgets -> + if (NotesPreferences.getRememberedFolderId(this) == folder.id) { + NotesPreferences.clearRememberedFolder(this) + } + updateWidgets(widgets) + notesListViewModel.refresh(this) + } + } + + private fun confirmFolderDialog() { + val dialogState = folderDialogState ?: return + notesListViewModel.saveFolder( + context = this, + dialogState = dialogState, + onDuplicateName = { + Toast.makeText( + this, + getString(R.string.folder_exist, dialogState.name.trim()), + Toast.LENGTH_LONG + ).show() + }, + onComplete = { success -> + if (success) { + dialogState.folder?.takeIf { !dialogState.create }?.let { folder -> + if (NotesPreferences.getRememberedFolderId(this) == folder.id) { + rememberFolder(folder.id, dialogState.name.trim()) + } + } + folderDialogState = null + notesListViewModel.refresh(this) + } + } + ) + } + + private fun updateWidgets(widgets: Set) { + NoteWidgetUpdater.updateWidgets(this, widgets) + } + + private fun startPreferenceActivity() { + startActivity(Intent(this, NotesPreferenceActivity::class.java)) + } + + private fun restoreLastFolder() { + val folderId = NotesPreferences.getRememberedFolderId(this) + if (folderId == Notes.ID_ROOT_FOLDER.toLong()) { + return + } + + notesListViewModel.restoreFolder( + folderId = folderId, + folderTitle = NotesPreferences.getRememberedFolderTitle(this) + ) + } + + private fun rememberFolder(folderId: Long, folderTitle: String) { + NotesPreferences.rememberLastFolder(this, folderId, folderTitle) + } + + private fun setAppInfoFromRawRes() { + val preferences = defaultPreferences() + if (preferences.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { + return + } + + val introduction = runCatching { + resources.openRawResource(R.raw.introduction).bufferedReader().use { it.readText() } + }.getOrElse { error -> + Log.e(TAG, "Read introduction file error", error) + return + } + + val note = WorkingNote.createEmptyNote( + this, + Notes.ID_ROOT_FOLDER.toLong(), + AppWidgetManager.INVALID_APPWIDGET_ID, + Notes.TYPE_WIDGET_INVALIDE, + ResourceParser.getDefaultBgId(this) + ) + note.setWorkingText(introduction) + if (note.saveNote()) { + preferences.edit { putBoolean(PREFERENCE_ADD_INTRODUCTION, true) } + } else { + Log.e(TAG, "Save introduction note error") + } + } + + companion object { + private const val TAG = "NotesListActivity" + private const val PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction" + } +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/NotesListScreen.kt b/app/src/main/kotlin/net/micode/notes/ui/NotesListScreen.kt new file mode 100644 index 00000000..6ae6c859 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/NotesListScreen.kt @@ -0,0 +1,635 @@ +package net.micode.notes.ui + +import androidx.activity.compose.BackHandler +import androidx.annotation.DrawableRes +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.Image +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import net.micode.notes.R +import net.micode.notes.data.Notes +import net.micode.notes.tool.ResourceParser +import java.text.DateFormat +import java.util.Date + +data class FolderDialogUiState( + val create: Boolean, + val name: String, + val folder: NotesListItemUi? = null +) + +@Composable +fun NotesListScreen( + state: NotesListUiState, + folderDialogState: FolderDialogUiState?, + onBack: () -> Unit, + onSearchTextChange: (String) -> Unit, + onItemClick: (NotesListItemUi) -> Unit, + onItemLongClick: (NotesListItemUi) -> Unit, + onCreateNote: () -> Unit, + onCreateFolder: () -> Unit, + onDeleteSelection: () -> Unit, + onClearSelection: () -> Unit, + onRenameFolder: (NotesListItemUi) -> Unit, + onDeleteFolder: (NotesListItemUi) -> Unit, + onOpenSettings: () -> Unit, + onFolderDialogNameChange: (String) -> Unit, + onDismissFolderDialog: () -> Unit, + onConfirmFolderDialog: () -> Unit +) { + val selectionActive = state.selectedIds.isNotEmpty() + val screenTitle = when { + selectionActive -> pluralStringResource( + R.plurals.menu_select_title, + state.selectedIds.size, + state.selectedIds.size + ) + state.isRoot -> stringResource(R.string.app_name) + state.isCallRecordFolder -> stringResource(R.string.call_record_folder_name) + else -> state.currentFolderTitle.ifBlank { stringResource(R.string.app_name) } + } + + BackHandler(enabled = selectionActive || !state.isRoot) { + onBack() + } + + Box(modifier = Modifier.fillMaxSize()) { + ResourceDrawableBackground( + resId = R.drawable.list_background, + modifier = Modifier.fillMaxSize() + ) + Column( + modifier = Modifier.fillMaxSize() + ) { + NotesHeader( + title = screenTitle, + showBack = selectionActive || !state.isRoot, + showSettings = state.isRoot && !selectionActive, + onBack = onBack, + onOpenSettings = onOpenSettings + ) + SearchBar( + value = state.searchText, + onValueChange = onSearchTextChange + ) + Box(modifier = Modifier.weight(1f)) { + if (state.items.isEmpty() && !state.isLoading) { + EmptyState(isCallRecordFolder = state.isCallRecordFolder) + } else { + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + items( + items = state.items, + key = { item -> item.id } + ) { item -> + NoteListRow( + item = item, + isSelected = item.id in state.selectedIds, + selectionActive = selectionActive, + onClick = { onItemClick(item) }, + onLongClick = { onItemLongClick(item) }, + onRenameFolder = { onRenameFolder(item) }, + onDeleteFolder = { onDeleteFolder(item) } + ) + } + item { + Footer( + itemCount = state.items.size, + isCallRecordFolder = state.isCallRecordFolder, + showAddHint = !selectionActive && !state.isCallRecordFolder + ) + } + } + } + } + BottomBar( + state = state, + onCreateNote = onCreateNote, + onCreateFolder = onCreateFolder, + onDeleteSelection = onDeleteSelection, + onClearSelection = onClearSelection + ) + } + + folderDialogState?.let { dialogState -> + AlertDialog( + onDismissRequest = onDismissFolderDialog, + title = { + Text( + text = stringResource( + if (dialogState.create) { + R.string.menu_create_folder + } else { + R.string.menu_folder_change_name + } + ) + ) + }, + text = { + OutlinedTextField( + value = dialogState.name, + onValueChange = onFolderDialogNameChange, + singleLine = true, + modifier = Modifier.fillMaxWidth(), + placeholder = { Text(text = stringResource(R.string.hint_foler_name)) } + ) + }, + confirmButton = { + TextButton( + onClick = onConfirmFolderDialog, + enabled = dialogState.name.isNotBlank() + ) { + Text(text = stringResource(android.R.string.ok)) + } + }, + dismissButton = { + TextButton(onClick = onDismissFolderDialog) { + Text(text = stringResource(android.R.string.cancel)) + } + } + ) + } + } +} + +@Composable +private fun NotesHeader( + title: String, + showBack: Boolean, + showSettings: Boolean, + onBack: () -> Unit, + onOpenSettings: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .statusBarsPadding() + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + if (showBack) { + TextButton(onClick = onBack) { + Text(text = stringResource(R.string.action_back)) + } + } else { + Spacer(modifier = Modifier.width(12.dp)) + } + + Text( + text = title, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f), + color = Color(0xFF1F1A14) + ) + + if (showSettings) { + TextButton(onClick = onOpenSettings) { + Text(text = stringResource(R.string.menu_setting)) + } + } else { + Spacer(modifier = Modifier.width(12.dp)) + } + } +} + +@Composable +private fun SearchBar( + value: String, + onValueChange: (String) -> Unit +) { + val shape = RoundedCornerShape(18.dp) + Surface( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp) + .border(width = 1.dp, color = Color(0x33B58B4B), shape = shape), + shape = shape, + color = Color(0x55FFF7E8) + ) { + BasicTextField( + value = value, + onValueChange = onValueChange, + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Search), + textStyle = MaterialTheme.typography.bodyLarge.copy(color = Color(0xFF2A261F)), + cursorBrush = SolidColor(Color(0xFF8D5F23)), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 18.dp, vertical = 14.dp), + decorationBox = { innerTextField -> + Box(modifier = Modifier.fillMaxWidth()) { + if (value.isEmpty()) { + Text( + text = stringResource(R.string.search_hint), + style = MaterialTheme.typography.bodyLarge, + color = Color(0x886E6253) + ) + } + innerTextField() + } + } + ) + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun NoteListRow( + item: NotesListItemUi, + isSelected: Boolean, + selectionActive: Boolean, + onClick: () -> Unit, + onLongClick: () -> Unit, + onRenameFolder: () -> Unit, + onDeleteFolder: () -> Unit +) { + var menuExpanded by remember(item.id) { mutableStateOf(false) } + + Surface( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(22.dp)) + .combinedClickable( + onClick = onClick, + onLongClick = { + if (!item.isFolder) { + onLongClick() + } + } + ) + .then( + if (isSelected) { + Modifier.border( + width = 2.dp, + color = Color(0xFF8D5F23), + shape = RoundedCornerShape(22.dp) + ) + } else { + Modifier + } + ), + color = noteSurfaceColor(item), + tonalElevation = if (isSelected) 6.dp else 1.dp, + shadowElevation = if (isSelected) 8.dp else 2.dp + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 18.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + RowBadge( + resId = when { + isSelected -> R.drawable.selected + item.id == Notes.ID_CALL_RECORD_FOLDER.toLong() -> R.drawable.call_record + else -> null + }, + tintColor = when { + item.isFolder -> Color(0xFF9C7A3D) + else -> Color(0xFFCDB78C) + } + ) + Spacer(modifier = Modifier.width(14.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = rowTitle(item), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = rowSubtitle(item), + style = MaterialTheme.typography.bodyMedium, + color = Color(0xFF5B5344), + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) + } + if (item.type == Notes.TYPE_FOLDER && !selectionActive) { + Box { + TextButton(onClick = { menuExpanded = true }) { + Text(text = stringResource(R.string.action_more)) + } + DropdownMenu( + expanded = menuExpanded, + onDismissRequest = { menuExpanded = false } + ) { + DropdownMenuItem( + text = { Text(text = stringResource(R.string.menu_folder_change_name)) }, + onClick = { + menuExpanded = false + onRenameFolder() + } + ) + DropdownMenuItem( + leadingIcon = { + Image( + painter = painterResource(R.drawable.menu_delete), + contentDescription = null, + modifier = Modifier.size(18.dp) + ) + }, + text = { Text(text = stringResource(R.string.menu_folder_delete)) }, + onClick = { + menuExpanded = false + onDeleteFolder() + } + ) + } + } + } else if (isSelected) { + Image( + painter = painterResource(R.drawable.menu_delete), + contentDescription = stringResource(R.string.menu_delete), + modifier = Modifier.size(22.dp) + ) + } + } + } +} + +@Composable +private fun RowBadge( + @DrawableRes resId: Int?, + tintColor: Color +) { + if (resId != null) { + Image( + painter = painterResource(resId), + contentDescription = null, + modifier = Modifier.size(width = 20.dp, height = 24.dp) + ) + } else { + Box( + modifier = Modifier + .size(14.dp) + .clip(RoundedCornerShape(7.dp)) + .background(tintColor) + ) + } +} + +@Composable +private fun Footer( + itemCount: Int, + isCallRecordFolder: Boolean, + showAddHint: Boolean +) { + val title = if (isCallRecordFolder) { + if (itemCount > 0) { + pluralStringResource(R.plurals.footer_call_notes_count, itemCount, itemCount) + } else { + stringResource(R.string.footer_call_notes_empty) + } + } else { + if (itemCount > 0) { + pluralStringResource(R.plurals.footer_items_count, itemCount, itemCount) + } else { + stringResource(R.string.footer_items_empty) + } + } + + val hint = if (showAddHint) { + stringResource(R.string.footer_hint_add_note) + } else { + stringResource(R.string.footer_hint_search) + } + + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp, horizontal = 4.dp) + ) { + Text( + text = title, + style = MaterialTheme.typography.titleSmall, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(4.dp)) + Text( + text = hint, + style = MaterialTheme.typography.bodyMedium, + color = Color(0xFF6A6256) + ) + } +} + +@Composable +private fun EmptyState(isCallRecordFolder: Boolean) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = if (isCallRecordFolder) { + stringResource(R.string.footer_call_notes_empty) + } else { + stringResource(R.string.footer_items_empty) + }, + style = MaterialTheme.typography.headlineSmall, + color = Color(0xFF746B5A) + ) + } +} + +@Composable +private fun BottomBar( + state: NotesListUiState, + onCreateNote: () -> Unit, + onCreateFolder: () -> Unit, + onDeleteSelection: () -> Unit, + onClearSelection: () -> Unit +) { + val selectionActive = state.selectedIds.isNotEmpty() + val showPrimaryAction = !state.isCallRecordFolder + val showSecondaryActions = state.isRoot + + if (!selectionActive && !showPrimaryAction && !showSecondaryActions) { + return + } + + if (selectionActive) { + Row( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(horizontal = 16.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + FilledTonalButton( + modifier = Modifier.weight(1f), + onClick = onClearSelection + ) { + Text(text = stringResource(android.R.string.cancel)) + } + FilledTonalButton( + modifier = Modifier.weight(1f), + onClick = onDeleteSelection + ) { + Text(text = stringResource(R.string.menu_delete)) + } + } + return + } + + Column( + modifier = Modifier + .fillMaxWidth() + .navigationBarsPadding() + .padding(horizontal = 16.dp, vertical = 12.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + if (showPrimaryAction) { + NewNoteButton( + modifier = Modifier.fillMaxWidth(), + label = stringResource(R.string.notelist_menu_new), + onClick = onCreateNote + ) + } + + if (showSecondaryActions) { + FilledTonalButton( + modifier = Modifier.fillMaxWidth(), + onClick = onCreateFolder + ) { + Text(text = stringResource(R.string.menu_create_folder)) + } + } + } +} + +@Composable +private fun NewNoteButton( + modifier: Modifier = Modifier, + label: String, + onClick: () -> Unit +) { + val interactionSource = remember { MutableInteractionSource() } + val pressed by interactionSource.collectIsPressedAsState() + val backgroundResId = if (pressed) { + R.drawable.new_note_pressed + } else { + R.drawable.new_note_normal + } + + Box( + modifier = modifier + .height(52.dp) + .clip(RoundedCornerShape(12.dp)) + .semantics { + contentDescription = label + role = Role.Button + } + .clickable( + interactionSource = interactionSource, + indication = null, + onClick = onClick + ), + contentAlignment = Alignment.Center + ) { + Image( + painter = painterResource(backgroundResId), + contentDescription = null, + contentScale = ContentScale.FillBounds, + modifier = Modifier.fillMaxSize() + ) + } +} + +@Composable +private fun rowTitle(item: NotesListItemUi): String { + return when { + item.id == Notes.ID_CALL_RECORD_FOLDER.toLong() -> stringResource(R.string.call_record_folder_name) + item.title.isNotBlank() -> item.title + else -> stringResource(R.string.notelist_string_info) + } +} + +@Composable +private fun rowSubtitle(item: NotesListItemUi): String { + return if (item.isFolder) { + stringResource(R.string.format_folder_files_count, item.notesCount) + } else { + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT) + .format(Date(item.modifiedDate)) + } +} + +private fun noteSurfaceColor(item: NotesListItemUi): Color { + return if (item.isFolder) { + Color(0xFFE8D9B5) + } else { + when (item.bgColorId) { + ResourceParser.YELLOW -> Color(0xFFFFF3BF) + ResourceParser.BLUE -> Color(0xFFDCEBFF) + ResourceParser.WHITE -> Color(0xFFF7F4EC) + ResourceParser.GREEN -> Color(0xFFE1F2D8) + ResourceParser.RED -> Color(0xFFFFDED7) + else -> Color(0xFFFFF3BF) + } + } +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/NotesListViewModel.kt b/app/src/main/kotlin/net/micode/notes/ui/NotesListViewModel.kt new file mode 100644 index 00000000..142d5e42 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/NotesListViewModel.kt @@ -0,0 +1,272 @@ +package net.micode.notes.ui + +import android.content.Context +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import net.micode.notes.data.Notes +import net.micode.notes.data.NotesRepository +import net.micode.notes.data.room.NoteListRow +import net.micode.notes.tool.NotesPreferences + +data class NotesListItemUi( + val id: Long, + val title: String, + val type: Int, + val folderId: Long, + val modifiedDate: Long, + val notesCount: Int, + val bgColorId: Int, + val hasAttachment: Boolean, + val hasAlert: Boolean, + val widget: AppWidgetAttribute +) { + val isFolder: Boolean + get() = type == Notes.TYPE_FOLDER || type == Notes.TYPE_SYSTEM +} + +data class NotesListUiState( + val currentFolderId: Long = Notes.ID_ROOT_FOLDER.toLong(), + val currentFolderTitle: String = "", + val searchText: String = "", + val items: List = emptyList(), + val selectedIds: Set = emptySet(), + val isLoading: Boolean = false +) { + val isRoot: Boolean + get() = currentFolderId == Notes.ID_ROOT_FOLDER.toLong() + + val isCallRecordFolder: Boolean + get() = currentFolderId == Notes.ID_CALL_RECORD_FOLDER.toLong() +} + +class NotesListViewModel : ViewModel() { + private val _uiState = MutableStateFlow(NotesListUiState()) + val uiState = _uiState.asStateFlow() + + private var queryGeneration = 0L + + fun refresh(context: Context) { + val generation = ++queryGeneration + val currentState = _uiState.value + _uiState.update { it.copy(isLoading = true) } + + viewModelScope.launch(Dispatchers.IO) { + val items = NotesRepository(context).loadListItems( + folderId = currentState.currentFolderId, + searchText = currentState.searchText, + sortMode = NotesPreferences.getListSortMode(context) + ).map { item -> + item.toUi() + } + + withContext(Dispatchers.Main) { + if (generation != queryGeneration) { + return@withContext + } + + val validSelectedIds = currentState.selectedIds.intersect(items.mapTo(hashSetOf()) { it.id }) + _uiState.update { + it.copy( + items = items, + selectedIds = validSelectedIds, + isLoading = false + ) + } + } + } + } + + fun setSearchText(context: Context, searchText: String) { + _uiState.update { + it.copy( + searchText = searchText, + selectedIds = emptySet() + ) + } + refresh(context) + } + + fun openFolder(context: Context, item: NotesListItemUi) { + _uiState.update { + it.copy( + currentFolderId = item.id, + currentFolderTitle = item.title, + selectedIds = emptySet() + ) + } + refresh(context) + } + + fun restoreFolder(folderId: Long, folderTitle: String) { + _uiState.update { + it.copy( + currentFolderId = folderId, + currentFolderTitle = folderTitle, + selectedIds = emptySet() + ) + } + } + + fun goToRoot(context: Context) { + _uiState.update { + it.copy( + currentFolderId = Notes.ID_ROOT_FOLDER.toLong(), + currentFolderTitle = "", + selectedIds = emptySet() + ) + } + refresh(context) + } + + fun clearSelection() { + _uiState.update { it.copy(selectedIds = emptySet()) } + } + + fun toggleSelection(itemId: Long) { + _uiState.update { state -> + val selectedIds = state.selectedIds.toMutableSet() + if (!selectedIds.add(itemId)) { + selectedIds.remove(itemId) + } + state.copy(selectedIds = selectedIds) + } + } + + fun deleteSelectedNotes( + context: Context, + onComplete: (Set) -> Unit + ) { + val selectedIds = _uiState.value.selectedIds + if (selectedIds.isEmpty()) { + onComplete(emptySet()) + return + } + + val selectedWidgets = _uiState.value.items + .filter { it.id in selectedIds } + .mapTo(linkedSetOf()) { it.widget } + + viewModelScope.launch(Dispatchers.IO) { + val success = NotesRepository(context).deleteNotes(selectedIds) + + if (!success) { + Log.e(TAG, "Failed to delete selected notes") + } + + withContext(Dispatchers.Main) { + _uiState.update { it.copy(selectedIds = emptySet()) } + onComplete(selectedWidgets) + } + } + } + + fun deleteFolder( + context: Context, + folderId: Long, + onComplete: (Set) -> Unit + ) { + if (folderId == Notes.ID_ROOT_FOLDER.toLong()) { + Log.e(TAG, "Wrong folder id, should not happen: $folderId") + onComplete(emptySet()) + return + } + + viewModelScope.launch(Dispatchers.IO) { + val repository = NotesRepository(context) + val widgets = repository.collectFolderWidgets(folderId) + val success = repository.deleteFolder(folderId) + + if (!success) { + Log.e(TAG, "Failed to delete folder $folderId") + } + + withContext(Dispatchers.Main) { + onComplete(widgets) + } + } + } + + fun saveFolder( + context: Context, + dialogState: FolderDialogUiState, + onDuplicateName: () -> Unit, + onComplete: (Boolean) -> Unit + ) { + val name = dialogState.name.trim() + if (name.isEmpty()) { + onComplete(false) + return + } + + viewModelScope.launch(Dispatchers.IO) { + val repository = NotesRepository(context) + val duplicateName = (dialogState.create || !name.contentEquals(dialogState.folder?.title)) && + repository.hasVisibleFolderNamed(name) + if (duplicateName) { + withContext(Dispatchers.Main) { + onDuplicateName() + } + return@launch + } + + val success = if (dialogState.create) { + repository.createFolder(name) > 0 + } else { + val folderId = dialogState.folder?.id + if (folderId == null) { + Log.e(TAG, "Missing folder while renaming") + false + } else { + repository.renameFolder(folderId, name) + } + } + + if (!success) { + Log.e(TAG, "Failed to save folder") + } + + withContext(Dispatchers.Main) { + if (success && !dialogState.create && _uiState.value.currentFolderId == dialogState.folder?.id) { + _uiState.update { state -> + state.copy(currentFolderTitle = name) + } + } + onComplete(success) + } + } + } + + companion object { + private const val TAG = "NotesListViewModel" + } +} + +private fun NoteListRow.toUi(): NotesListItemUi { + val title = snippet + .replace(NoteEditActivity.TAG_CHECKED, "") + .replace(NoteEditActivity.TAG_UNCHECKED, "") + .trim() + + return NotesListItemUi( + id = id, + title = title, + type = type, + folderId = parentId, + modifiedDate = modifiedDate, + notesCount = notesCount, + bgColorId = bgColorId, + hasAttachment = hasAttachment > 0, + hasAlert = alertedDate > 0L, + widget = AppWidgetAttribute( + widgetId = widgetId, + widgetType = widgetType + ) + ) +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/NotesPreferenceActivity.kt b/app/src/main/kotlin/net/micode/notes/ui/NotesPreferenceActivity.kt new file mode 100644 index 00000000..693f518d --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/NotesPreferenceActivity.kt @@ -0,0 +1,60 @@ +package net.micode.notes.ui + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.core.view.WindowCompat +import net.micode.notes.tool.NotesPreferences + +class NotesPreferenceActivity : ComponentActivity() { + private var uiState by mutableStateOf(NotesSettingsUiState()) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + WindowCompat.setDecorFitsSystemWindows(window, false) + uiState = loadUiState() + + setContent { + MaterialTheme { + NotesPreferenceScreen( + state = uiState, + onBack = { finish() }, + onToggleRandomBackground = { enabled -> + NotesPreferences.setRandomBackgroundEnabled(this, enabled) + uiState = uiState.copy(randomBackgroundEnabled = enabled) + }, + onSelectDefaultBackgroundColor = { colorId -> + NotesPreferences.setDefaultBackgroundColor(this, colorId) + uiState = uiState.copy(defaultBackgroundColor = colorId) + }, + onSelectListSortMode = { sortMode -> + NotesPreferences.setListSortMode(this, sortMode) + uiState = uiState.copy(listSortMode = sortMode) + }, + onToggleDeleteConfirmation = { enabled -> + NotesPreferences.setDeleteConfirmationEnabled(this, enabled) + uiState = uiState.copy(deleteConfirmationEnabled = enabled) + }, + onToggleRememberLastFolder = { enabled -> + NotesPreferences.setRememberLastFolderEnabled(this, enabled) + uiState = uiState.copy(rememberLastFolderEnabled = enabled) + } + ) + } + } + } + + private fun loadUiState(): NotesSettingsUiState { + return NotesSettingsUiState( + randomBackgroundEnabled = NotesPreferences.isRandomBackgroundEnabled(this), + defaultBackgroundColor = NotesPreferences.getDefaultBackgroundColor(this), + listSortMode = NotesPreferences.getListSortMode(this), + deleteConfirmationEnabled = NotesPreferences.isDeleteConfirmationEnabled(this), + rememberLastFolderEnabled = NotesPreferences.isRememberLastFolderEnabled(this) + ) + } +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/NotesPreferenceScreen.kt b/app/src/main/kotlin/net/micode/notes/ui/NotesPreferenceScreen.kt new file mode 100644 index 00000000..4f88eaf7 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/NotesPreferenceScreen.kt @@ -0,0 +1,342 @@ +package net.micode.notes.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import net.micode.notes.R +import net.micode.notes.tool.NotesSortMode +import net.micode.notes.tool.ResourceParser + +data class NotesSettingsUiState( + val randomBackgroundEnabled: Boolean = false, + val defaultBackgroundColor: Int = ResourceParser.BG_DEFAULT_COLOR, + val listSortMode: NotesSortMode = NotesSortMode.MODIFIED_DESC, + val deleteConfirmationEnabled: Boolean = true, + val rememberLastFolderEnabled: Boolean = true +) + +@Composable +fun NotesPreferenceScreen( + state: NotesSettingsUiState, + onBack: () -> Unit, + onToggleRandomBackground: (Boolean) -> Unit, + onSelectDefaultBackgroundColor: (Int) -> Unit, + onSelectListSortMode: (NotesSortMode) -> Unit, + onToggleDeleteConfirmation: (Boolean) -> Unit, + onToggleRememberLastFolder: (Boolean) -> Unit +) { + Box(modifier = Modifier.fillMaxSize()) { + ResourceDrawableBackground( + resId = R.drawable.list_background, + modifier = Modifier.fillMaxSize() + ) + Column(modifier = Modifier.fillMaxSize()) { + Row( + modifier = Modifier + .fillMaxWidth() + .statusBarsPadding() + .padding(horizontal = 12.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically + ) { + TextButton(onClick = onBack) { + Text(text = stringResource(R.string.action_back)) + } + Text( + text = stringResource(R.string.preferences_title), + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.SemiBold + ) + } + + Column( + modifier = Modifier + .weight(1f) + .padding(horizontal = 16.dp), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + SettingSwitchCard( + title = stringResource(R.string.preferences_bg_random_appear_title), + summary = if (state.randomBackgroundEnabled) { + stringResource(R.string.state_enabled) + } else { + stringResource(R.string.state_disabled) + }, + checked = state.randomBackgroundEnabled, + onCheckedChange = onToggleRandomBackground + ) + + Card( + shape = RoundedCornerShape(24.dp), + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + verticalArrangement = Arrangement.spacedBy(14.dp) + ) { + Text( + text = stringResource(R.string.preferences_default_color_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold + ) + Text( + text = colorLabel(state.defaultBackgroundColor), + style = MaterialTheme.typography.bodyMedium + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(12.dp) + ) { + noteColorOptions().forEach { option -> + ColorOptionButton( + modifier = Modifier.weight(1f), + option = option, + selected = option.colorId == state.defaultBackgroundColor, + onClick = { onSelectDefaultBackgroundColor(option.colorId) } + ) + } + } + } + } + + Card( + shape = RoundedCornerShape(24.dp), + modifier = Modifier.fillMaxWidth() + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text( + text = stringResource(R.string.preferences_sort_title), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold + ) + sortOptions().forEach { option -> + SelectionRow( + label = stringResource(option.labelRes), + selected = state.listSortMode == option.sortMode, + onClick = { onSelectListSortMode(option.sortMode) } + ) + } + } + } + + SettingSwitchCard( + title = stringResource(R.string.preferences_delete_confirmation_title), + summary = if (state.deleteConfirmationEnabled) { + stringResource(R.string.state_enabled) + } else { + stringResource(R.string.state_disabled) + }, + checked = state.deleteConfirmationEnabled, + onCheckedChange = onToggleDeleteConfirmation + ) + + SettingSwitchCard( + title = stringResource(R.string.preferences_remember_last_folder_title), + summary = if (state.rememberLastFolderEnabled) { + stringResource(R.string.state_enabled) + } else { + stringResource(R.string.state_disabled) + }, + checked = state.rememberLastFolderEnabled, + onCheckedChange = onToggleRememberLastFolder + ) + } + + Spacer( + modifier = Modifier + .navigationBarsPadding() + .size(1.dp) + ) + } + } +} + +@Composable +private fun SettingSwitchCard( + title: String, + summary: String, + checked: Boolean, + onCheckedChange: (Boolean) -> Unit +) { + Card( + shape = RoundedCornerShape(24.dp), + modifier = Modifier.fillMaxWidth() + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.SemiBold + ) + Spacer(modifier = Modifier.height(6.dp)) + Text( + text = summary, + style = MaterialTheme.typography.bodyMedium + ) + } + Switch( + checked = checked, + onCheckedChange = onCheckedChange + ) + } + } +} + +@Composable +private fun ColorOptionButton( + modifier: Modifier, + option: NoteColorOption, + selected: Boolean, + onClick: () -> Unit +) { + Surface( + modifier = modifier, + shape = RoundedCornerShape(18.dp), + color = if (selected) { + Color(0x1AB58B4B) + } else { + Color(0x0FFFFFFF) + }, + border = BorderStroke( + width = if (selected) 2.dp else 1.dp, + color = if (selected) Color(0xFF8D5F23) else Color(0x33B58B4B) + ) + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(vertical = 12.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + Box( + modifier = Modifier + .size(24.dp) + .clip(CircleShape) + .background(option.previewColor) + ) + Text( + text = stringResource(option.labelRes), + style = MaterialTheme.typography.bodySmall + ) + } + } +} + +@Composable +private fun SelectionRow( + label: String, + selected: Boolean, + onClick: () -> Unit +) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(18.dp), + color = if (selected) Color(0x1AB58B4B) else Color(0x10FFFFFF), + border = BorderStroke( + width = if (selected) 2.dp else 1.dp, + color = if (selected) Color(0xFF8D5F23) else Color(0x33B58B4B) + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onClick) + .padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Box( + modifier = Modifier + .size(18.dp) + .clip(CircleShape) + .background( + if (selected) { + Color(0xFF8D5F23) + } else { + Color(0x33B58B4B) + } + ) + ) + Spacer(modifier = Modifier.size(10.dp)) + Text( + text = label, + style = MaterialTheme.typography.bodyLarge + ) + } + } +} + +@Composable +private fun colorLabel(colorId: Int): String { + return stringResource(noteColorOptions().firstOrNull { it.colorId == colorId }?.labelRes ?: R.string.color_yellow) +} + +private data class NoteColorOption( + val colorId: Int, + val previewColor: Color, + val labelRes: Int +) + +private data class SortOption( + val sortMode: NotesSortMode, + val labelRes: Int +) + +private fun noteColorOptions(): List { + return listOf( + NoteColorOption(ResourceParser.YELLOW, Color(0xFFFFF3BF), R.string.color_yellow), + NoteColorOption(ResourceParser.BLUE, Color(0xFFDCEBFF), R.string.color_blue), + NoteColorOption(ResourceParser.WHITE, Color(0xFFF7F4EC), R.string.color_white), + NoteColorOption(ResourceParser.GREEN, Color(0xFFE1F2D8), R.string.color_green), + NoteColorOption(ResourceParser.RED, Color(0xFFFFDED7), R.string.color_red) + ) +} + +private fun sortOptions(): List { + return listOf( + SortOption(NotesSortMode.MODIFIED_DESC, R.string.preferences_sort_modified_desc), + SortOption(NotesSortMode.MODIFIED_ASC, R.string.preferences_sort_modified_asc), + SortOption(NotesSortMode.TITLE_ASC, R.string.preferences_sort_title_asc) + ) +} diff --git a/app/src/main/kotlin/net/micode/notes/ui/ResourceDrawableBackground.kt b/app/src/main/kotlin/net/micode/notes/ui/ResourceDrawableBackground.kt new file mode 100644 index 00000000..d21f837e --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/ui/ResourceDrawableBackground.kt @@ -0,0 +1,26 @@ +package net.micode.notes.ui + +import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.viewinterop.AndroidView + +@Composable +fun ResourceDrawableBackground( + @DrawableRes resId: Int, + modifier: Modifier = Modifier +) { + AndroidView( + modifier = modifier, + factory = { context -> + ImageView(context).apply { + scaleType = ImageView.ScaleType.FIT_XY + setImageResource(resId) + } + }, + update = { view -> + view.setImageResource(resId) + } + ) +} diff --git a/app/src/main/kotlin/net/micode/notes/widget/NoteWidgetProvider.kt b/app/src/main/kotlin/net/micode/notes/widget/NoteWidgetProvider.kt new file mode 100644 index 00000000..d1879d17 --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/widget/NoteWidgetProvider.kt @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.micode.notes.widget + +import android.appwidget.AppWidgetManager +import android.content.Context +import android.content.Intent +import android.util.Log +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.glance.GlanceId +import androidx.glance.GlanceModifier +import androidx.glance.Image +import androidx.glance.ImageProvider +import androidx.glance.action.clickable +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetManager +import androidx.glance.appwidget.action.actionStartActivity +import androidx.glance.appwidget.provideContent +import androidx.glance.layout.Box +import androidx.glance.layout.ContentScale +import androidx.glance.layout.fillMaxSize +import androidx.glance.layout.padding +import androidx.glance.text.Text +import androidx.glance.text.TextStyle +import androidx.glance.unit.ColorProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import net.micode.notes.R +import net.micode.notes.data.Notes +import net.micode.notes.data.NotesRepository +import net.micode.notes.tool.ResourceParser +import net.micode.notes.ui.AppWidgetAttribute +import net.micode.notes.ui.NoteEditActivity + +internal data class NoteWidgetLayoutSpec( + val topPadding: androidx.compose.ui.unit.Dp, + val maxLines: Int +) + +private data class NoteWidgetModel( + val backgroundResId: Int, + val text: String, + val intent: Intent +) + +internal abstract class NoteGlanceWidget( + private val widgetType: Int, + private val layoutSpec: NoteWidgetLayoutSpec +) : GlanceAppWidget() { + override suspend fun provideGlance(context: Context, id: GlanceId) { + val appWidgetId = GlanceAppWidgetManager(context).getAppWidgetId(id) + val model = withContext(Dispatchers.IO) { + loadWidgetModel(context, appWidgetId, widgetType) + } + + provideContent { + NoteWidgetContent( + model = model, + layoutSpec = layoutSpec + ) + } + } + + override suspend fun onDelete(context: Context, glanceId: GlanceId) { + val appWidgetId = GlanceAppWidgetManager(context).getAppWidgetId(glanceId) + clearWidgetBinding(context, appWidgetId) + super.onDelete(context, glanceId) + } + + protected abstract fun getBackgroundResource(bgId: Int): Int + + private suspend fun loadWidgetModel( + context: Context, + appWidgetId: Int, + widgetType: Int + ): NoteWidgetModel { + val repository = NotesRepository(context.applicationContext) + var bgId = ResourceParser.getDefaultBgId(context) + val intent = Intent(context, NoteEditActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP + putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetId) + putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, widgetType) + } + + val notes = repository.getNotesByWidgetId(appWidgetId) + val snippet = notes.firstOrNull()?.let { note -> + if (notes.size > 1) { + Log.e(TAG, "Multiple notes found with widget id: $appWidgetId") + } + + bgId = note.bgColorId + intent.putExtra(Intent.EXTRA_UID, note.id) + intent.action = Intent.ACTION_VIEW + note.snippet + } ?: run { + intent.action = Intent.ACTION_INSERT_OR_EDIT + context.getString(R.string.widget_havenot_content) + } + + intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId) + + return NoteWidgetModel( + backgroundResId = getBackgroundResource(bgId), + text = snippet, + intent = intent + ) + } + + companion object { + private const val TAG = "NoteWidgetProvider" + + private suspend fun clearWidgetBinding(context: Context, appWidgetId: Int) { + NotesRepository(context.applicationContext).clearWidgetBinding( + appWidgetId = appWidgetId, + invalidWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + ) + } + } +} + +internal object NoteWidget2x : NoteGlanceWidget( + widgetType = Notes.TYPE_WIDGET_2X, + layoutSpec = NoteWidgetLayoutSpec(topPadding = 20.dp, maxLines = 6) +) { + override fun getBackgroundResource(bgId: Int): Int { + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId) + } +} + +internal object NoteWidget4x : NoteGlanceWidget( + widgetType = Notes.TYPE_WIDGET_4X, + layoutSpec = NoteWidgetLayoutSpec(topPadding = 40.dp, maxLines = 12) +) { + override fun getBackgroundResource(bgId: Int): Int { + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId) + } +} + +object NoteWidgetUpdater { + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + fun updateWidget(context: Context, appWidgetId: Int, widgetType: Int) { + scope.launch { + updateWidgetInternal(context.applicationContext, appWidgetId, widgetType) + } + } + + fun updateWidgets(context: Context, widgets: Set) { + scope.launch { + widgets + .filter { + it.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID && + it.widgetType != Notes.TYPE_WIDGET_INVALIDE + } + .forEach { widget -> + updateWidgetInternal( + context = context.applicationContext, + appWidgetId = widget.widgetId, + widgetType = widget.widgetType + ) + } + } + } + + private suspend fun updateWidgetInternal(context: Context, appWidgetId: Int, widgetType: Int) { + val widget = when (widgetType) { + Notes.TYPE_WIDGET_2X -> NoteWidget2x + Notes.TYPE_WIDGET_4X -> NoteWidget4x + else -> { + Log.e(TAG, "Unsupported widget type: $widgetType") + return + } + } + + val glanceId = runCatching { + GlanceAppWidgetManager(context).getGlanceIdBy(appWidgetId) + }.getOrElse { error -> + Log.e(TAG, "Unable to find GlanceId for widget id $appWidgetId", error) + return + } + + widget.update(context, glanceId) + } + + private const val TAG = "NoteWidgetUpdater" +} + +@Composable +private fun NoteWidgetContent( + model: NoteWidgetModel, + layoutSpec: NoteWidgetLayoutSpec +) { + Box( + modifier = GlanceModifier + .fillMaxSize() + .clickable(actionStartActivity(model.intent)) + ) { + Image( + provider = ImageProvider(model.backgroundResId), + contentDescription = null, + contentScale = ContentScale.FillBounds, + modifier = GlanceModifier.fillMaxSize() + ) + Text( + text = model.text, + modifier = GlanceModifier + .fillMaxSize() + .padding( + top = layoutSpec.topPadding, + start = 15.dp, + end = 15.dp + ), + maxLines = layoutSpec.maxLines, + style = TextStyle( + color = ColorProvider(Color(0xFF663300)), + fontSize = 14.sp + ) + ) + } +} diff --git a/src/net/micode/notes/gtask/exception/ActionFailureException.java b/app/src/main/kotlin/net/micode/notes/widget/NoteWidgetProvider_2x.kt similarity index 58% rename from src/net/micode/notes/gtask/exception/ActionFailureException.java rename to app/src/main/kotlin/net/micode/notes/widget/NoteWidgetProvider_2x.kt index 15504be9..4c1f4fd8 100644 --- a/src/net/micode/notes/gtask/exception/ActionFailureException.java +++ b/app/src/main/kotlin/net/micode/notes/widget/NoteWidgetProvider_2x.kt @@ -13,21 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package net.micode.notes.widget -package net.micode.notes.gtask.exception; +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver -public class ActionFailureException extends RuntimeException { - private static final long serialVersionUID = 4425249765923293627L; - - public ActionFailureException() { - super(); - } - - public ActionFailureException(String paramString) { - super(paramString); - } - - public ActionFailureException(String paramString, Throwable paramThrowable) { - super(paramString, paramThrowable); - } +class NoteWidgetProvider_2x : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget = NoteWidget2x } diff --git a/app/src/main/kotlin/net/micode/notes/widget/NoteWidgetProvider_4x.kt b/app/src/main/kotlin/net/micode/notes/widget/NoteWidgetProvider_4x.kt new file mode 100644 index 00000000..6a8b554e --- /dev/null +++ b/app/src/main/kotlin/net/micode/notes/widget/NoteWidgetProvider_4x.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.micode.notes.widget + +import androidx.glance.appwidget.GlanceAppWidget +import androidx.glance.appwidget.GlanceAppWidgetReceiver + +class NoteWidgetProvider_4x : GlanceAppWidgetReceiver() { + override val glanceAppWidget: GlanceAppWidget = NoteWidget4x +} diff --git a/res/drawable-hdpi/icon_app.png b/app/src/main/res/drawable-hdpi/icon_app.png similarity index 100% rename from res/drawable-hdpi/icon_app.png rename to app/src/main/res/drawable-hdpi/icon_app.png diff --git a/res/drawable-hdpi/notification.png b/app/src/main/res/drawable-hdpi/notification.png similarity index 100% rename from res/drawable-hdpi/notification.png rename to app/src/main/res/drawable-hdpi/notification.png diff --git a/app/src/main/res/drawable-mdpi/icon_app.png b/app/src/main/res/drawable-mdpi/icon_app.png new file mode 100644 index 00000000..a394943b Binary files /dev/null and b/app/src/main/res/drawable-mdpi/icon_app.png differ diff --git a/app/src/main/res/drawable-mdpi/notification.png b/app/src/main/res/drawable-mdpi/notification.png new file mode 100644 index 00000000..5f41b10a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/notification.png differ diff --git a/res/drawable-hdpi/bg_btn_set_color.png b/app/src/main/res/drawable-nodpi/bg_btn_set_color.png similarity index 100% rename from res/drawable-hdpi/bg_btn_set_color.png rename to app/src/main/res/drawable-nodpi/bg_btn_set_color.png diff --git a/res/drawable-hdpi/bg_color_btn_mask.png b/app/src/main/res/drawable-nodpi/bg_color_btn_mask.png similarity index 100% rename from res/drawable-hdpi/bg_color_btn_mask.png rename to app/src/main/res/drawable-nodpi/bg_color_btn_mask.png diff --git a/res/drawable-hdpi/call_record.png b/app/src/main/res/drawable-nodpi/call_record.png similarity index 100% rename from res/drawable-hdpi/call_record.png rename to app/src/main/res/drawable-nodpi/call_record.png diff --git a/res/drawable-hdpi/clock.png b/app/src/main/res/drawable-nodpi/clock.png similarity index 100% rename from res/drawable-hdpi/clock.png rename to app/src/main/res/drawable-nodpi/clock.png diff --git a/res/drawable-hdpi/delete.png b/app/src/main/res/drawable-nodpi/delete.png similarity index 100% rename from res/drawable-hdpi/delete.png rename to app/src/main/res/drawable-nodpi/delete.png diff --git a/res/drawable-hdpi/edit_blue.9.png b/app/src/main/res/drawable-nodpi/edit_blue.9.png similarity index 100% rename from res/drawable-hdpi/edit_blue.9.png rename to app/src/main/res/drawable-nodpi/edit_blue.9.png diff --git a/res/drawable-hdpi/edit_green.9.png b/app/src/main/res/drawable-nodpi/edit_green.9.png similarity index 100% rename from res/drawable-hdpi/edit_green.9.png rename to app/src/main/res/drawable-nodpi/edit_green.9.png diff --git a/res/drawable-hdpi/edit_red.9.png b/app/src/main/res/drawable-nodpi/edit_red.9.png similarity index 100% rename from res/drawable-hdpi/edit_red.9.png rename to app/src/main/res/drawable-nodpi/edit_red.9.png diff --git a/res/drawable-hdpi/edit_title_blue.9.png b/app/src/main/res/drawable-nodpi/edit_title_blue.9.png similarity index 100% rename from res/drawable-hdpi/edit_title_blue.9.png rename to app/src/main/res/drawable-nodpi/edit_title_blue.9.png diff --git a/res/drawable-hdpi/edit_title_green.9.png b/app/src/main/res/drawable-nodpi/edit_title_green.9.png similarity index 100% rename from res/drawable-hdpi/edit_title_green.9.png rename to app/src/main/res/drawable-nodpi/edit_title_green.9.png diff --git a/res/drawable-hdpi/edit_title_red.9.png b/app/src/main/res/drawable-nodpi/edit_title_red.9.png similarity index 100% rename from res/drawable-hdpi/edit_title_red.9.png rename to app/src/main/res/drawable-nodpi/edit_title_red.9.png diff --git a/res/drawable-hdpi/edit_title_white.9.png b/app/src/main/res/drawable-nodpi/edit_title_white.9.png similarity index 100% rename from res/drawable-hdpi/edit_title_white.9.png rename to app/src/main/res/drawable-nodpi/edit_title_white.9.png diff --git a/res/drawable-hdpi/edit_title_yellow.9.png b/app/src/main/res/drawable-nodpi/edit_title_yellow.9.png similarity index 100% rename from res/drawable-hdpi/edit_title_yellow.9.png rename to app/src/main/res/drawable-nodpi/edit_title_yellow.9.png diff --git a/res/drawable-hdpi/edit_white.9.png b/app/src/main/res/drawable-nodpi/edit_white.9.png similarity index 100% rename from res/drawable-hdpi/edit_white.9.png rename to app/src/main/res/drawable-nodpi/edit_white.9.png diff --git a/res/drawable-hdpi/edit_yellow.9.png b/app/src/main/res/drawable-nodpi/edit_yellow.9.png similarity index 100% rename from res/drawable-hdpi/edit_yellow.9.png rename to app/src/main/res/drawable-nodpi/edit_yellow.9.png diff --git a/res/drawable-hdpi/font_large.png b/app/src/main/res/drawable-nodpi/font_large.png similarity index 100% rename from res/drawable-hdpi/font_large.png rename to app/src/main/res/drawable-nodpi/font_large.png diff --git a/res/drawable-hdpi/font_normal.png b/app/src/main/res/drawable-nodpi/font_normal.png similarity index 100% rename from res/drawable-hdpi/font_normal.png rename to app/src/main/res/drawable-nodpi/font_normal.png diff --git a/res/drawable-hdpi/font_size_selector_bg.9.png b/app/src/main/res/drawable-nodpi/font_size_selector_bg.9.png similarity index 100% rename from res/drawable-hdpi/font_size_selector_bg.9.png rename to app/src/main/res/drawable-nodpi/font_size_selector_bg.9.png diff --git a/res/drawable-hdpi/font_small.png b/app/src/main/res/drawable-nodpi/font_small.png similarity index 100% rename from res/drawable-hdpi/font_small.png rename to app/src/main/res/drawable-nodpi/font_small.png diff --git a/res/drawable-hdpi/font_super.png b/app/src/main/res/drawable-nodpi/font_super.png similarity index 100% rename from res/drawable-hdpi/font_super.png rename to app/src/main/res/drawable-nodpi/font_super.png diff --git a/res/drawable-hdpi/list_background.png b/app/src/main/res/drawable-nodpi/list_background.png similarity index 100% rename from res/drawable-hdpi/list_background.png rename to app/src/main/res/drawable-nodpi/list_background.png diff --git a/res/drawable-hdpi/menu_delete.png b/app/src/main/res/drawable-nodpi/menu_delete.png similarity index 100% rename from res/drawable-hdpi/menu_delete.png rename to app/src/main/res/drawable-nodpi/menu_delete.png diff --git a/res/drawable-hdpi/menu_move.png b/app/src/main/res/drawable-nodpi/menu_move.png similarity index 100% rename from res/drawable-hdpi/menu_move.png rename to app/src/main/res/drawable-nodpi/menu_move.png diff --git a/res/drawable-hdpi/new_note_normal.png b/app/src/main/res/drawable-nodpi/new_note_normal.png similarity index 100% rename from res/drawable-hdpi/new_note_normal.png rename to app/src/main/res/drawable-nodpi/new_note_normal.png diff --git a/res/drawable-hdpi/new_note_pressed.png b/app/src/main/res/drawable-nodpi/new_note_pressed.png similarity index 100% rename from res/drawable-hdpi/new_note_pressed.png rename to app/src/main/res/drawable-nodpi/new_note_pressed.png diff --git a/res/drawable-hdpi/note_edit_color_selector_panel.png b/app/src/main/res/drawable-nodpi/note_edit_color_selector_panel.png similarity index 100% rename from res/drawable-hdpi/note_edit_color_selector_panel.png rename to app/src/main/res/drawable-nodpi/note_edit_color_selector_panel.png diff --git a/res/drawable-hdpi/selected.png b/app/src/main/res/drawable-nodpi/selected.png similarity index 100% rename from res/drawable-hdpi/selected.png rename to app/src/main/res/drawable-nodpi/selected.png diff --git a/res/drawable-hdpi/title_alert.png b/app/src/main/res/drawable-nodpi/title_alert.png similarity index 100% rename from res/drawable-hdpi/title_alert.png rename to app/src/main/res/drawable-nodpi/title_alert.png diff --git a/res/drawable-hdpi/widget_2x_blue.png b/app/src/main/res/drawable-nodpi/widget_2x_blue.png similarity index 100% rename from res/drawable-hdpi/widget_2x_blue.png rename to app/src/main/res/drawable-nodpi/widget_2x_blue.png diff --git a/res/drawable-hdpi/widget_2x_green.png b/app/src/main/res/drawable-nodpi/widget_2x_green.png similarity index 100% rename from res/drawable-hdpi/widget_2x_green.png rename to app/src/main/res/drawable-nodpi/widget_2x_green.png diff --git a/res/drawable-hdpi/widget_2x_red.png b/app/src/main/res/drawable-nodpi/widget_2x_red.png similarity index 100% rename from res/drawable-hdpi/widget_2x_red.png rename to app/src/main/res/drawable-nodpi/widget_2x_red.png diff --git a/res/drawable-hdpi/widget_2x_white.png b/app/src/main/res/drawable-nodpi/widget_2x_white.png similarity index 100% rename from res/drawable-hdpi/widget_2x_white.png rename to app/src/main/res/drawable-nodpi/widget_2x_white.png diff --git a/res/drawable-hdpi/widget_2x_yellow.png b/app/src/main/res/drawable-nodpi/widget_2x_yellow.png similarity index 100% rename from res/drawable-hdpi/widget_2x_yellow.png rename to app/src/main/res/drawable-nodpi/widget_2x_yellow.png diff --git a/res/drawable-hdpi/widget_4x_blue.png b/app/src/main/res/drawable-nodpi/widget_4x_blue.png similarity index 100% rename from res/drawable-hdpi/widget_4x_blue.png rename to app/src/main/res/drawable-nodpi/widget_4x_blue.png diff --git a/res/drawable-hdpi/widget_4x_green.png b/app/src/main/res/drawable-nodpi/widget_4x_green.png similarity index 100% rename from res/drawable-hdpi/widget_4x_green.png rename to app/src/main/res/drawable-nodpi/widget_4x_green.png diff --git a/res/drawable-hdpi/widget_4x_red.png b/app/src/main/res/drawable-nodpi/widget_4x_red.png similarity index 100% rename from res/drawable-hdpi/widget_4x_red.png rename to app/src/main/res/drawable-nodpi/widget_4x_red.png diff --git a/res/drawable-hdpi/widget_4x_white.png b/app/src/main/res/drawable-nodpi/widget_4x_white.png similarity index 100% rename from res/drawable-hdpi/widget_4x_white.png rename to app/src/main/res/drawable-nodpi/widget_4x_white.png diff --git a/res/drawable-hdpi/widget_4x_yellow.png b/app/src/main/res/drawable-nodpi/widget_4x_yellow.png similarity index 100% rename from res/drawable-hdpi/widget_4x_yellow.png rename to app/src/main/res/drawable-nodpi/widget_4x_yellow.png diff --git a/app/src/main/res/drawable-xhdpi/icon_app.png b/app/src/main/res/drawable-xhdpi/icon_app.png new file mode 100644 index 00000000..79618cec Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_app.png differ diff --git a/app/src/main/res/drawable-xhdpi/notification.png b/app/src/main/res/drawable-xhdpi/notification.png new file mode 100644 index 00000000..8046f2f8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/notification.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_app.png b/app/src/main/res/drawable-xxhdpi/icon_app.png new file mode 100644 index 00000000..6f18537d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_app.png differ diff --git a/app/src/main/res/drawable-xxhdpi/notification.png b/app/src/main/res/drawable-xxhdpi/notification.png new file mode 100644 index 00000000..ca7088a4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/notification.png differ diff --git a/res/raw-zh-rCN/introduction b/app/src/main/res/raw-zh-rCN/introduction similarity index 100% rename from res/raw-zh-rCN/introduction rename to app/src/main/res/raw-zh-rCN/introduction diff --git a/res/raw/introduction b/app/src/main/res/raw/introduction similarity index 100% rename from res/raw/introduction rename to app/src/main/res/raw/introduction diff --git a/app/src/main/res/values-v35/styles.xml b/app/src/main/res/values-v35/styles.xml new file mode 100644 index 00000000..b58a615c --- /dev/null +++ b/app/src/main/res/values-v35/styles.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..cf3ab4b4 --- /dev/null +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,103 @@ + + + + + + 便签 + 便签2x2 + 便签4x4 + 没有关联内容,点击新建便签。 + + 新建便签 + 知道了 + 查看 + + 新建文件夹 + 设置 + 返回 + 更多 + 已开启 + 已关闭 + 删除 + + 选中了 %d 项 + + 没有选中项,操作无效 + 进入清单模式 + 退出清单模式 + + 正常 + + 超大 + 刪除文件夹 + 修改文件夹名称 + 文件夹 %1$s 已存在,请重新命名 + 分享 + 分享便签 + 导出为 TXT + 保存为图片 + 发送到桌面 + 提醒我 + 删除提醒 + 已添加到桌面 + 删除 + + 确认要删除所选的 %d 条便签吗? + + 确认要删除该条便签吗? + 确认删除文件夹及所包含的便签吗? + + 导出文本时发生错误,请检查SD卡 + 没有可用于分享便签的应用 + 保存图片失败 + 要查看的便签不存在 + 空便签无法导出 + 不能为空便签设置闹钟提醒 + 不能将空便签发送到桌面 + 导出成功 + 已保存到相册 + + 设置 + 新建便签背景颜色随机 + 默认新建便签颜色 + 列表排序 + 最近修改优先 + 最早修改优先 + 按标题排序 + 删除前确认 + 记住上次打开的文件夹 + 黄色 + 蓝色 + 白色 + 绿色 + 红色 + 通话便签 + 请输入名称 + 搜索便签 + 写点什么… + 每行一项… + 这里还没有内容 + 还没有通话便签 + + 共 %1$d 项内容 + + + 共 %1$d 条通话便签 + + 轻触底部按钮即可新建便签 + 使用上方搜索可更快找到内容 + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..b1e28d3b --- /dev/null +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,103 @@ + + + + + + 便簽 + 便簽2x2 + 便簽4x4 + 沒有關聯內容,點擊新建便簽。 + + 新建便簽 + 知道了 + 查看 + + 新建文件夾 + 設置 + 返回 + 更多 + 已開啟 + 已關閉 + 刪除 + + 選中了 %d 項 + + 沒有選中項,操作無效 + 進入清單模式 + 退出清單模式 + + 正常 + + 超大 + 刪除文件夾 + 修改文件夾名稱 + 文件夾 %1$s 已存在,請重新命名 + 分享 + 分享便簽 + 導出為 TXT + 保存為圖片 + 發送到桌面 + 提醒我 + 刪除提醒 + 已添加到桌面 + 刪除 + + 确认要刪除所選的 %d 條便籤嗎? + + 确认要删除該條便籤嗎? + 確認刪除檔夾及所包含的便簽嗎? + 導出TXT時發生錯誤,請檢查SD卡 + 沒有可用於分享便簽的應用 + 保存圖片失敗 + 要查看的便籤不存在 + 空白便簽無法導出 + 不能爲空便籤設置鬧鐘提醒 + 不能將空便籤發送到桌面 + 導出成功 + 已保存到相冊 + + 設置 + 新建便籤背景顏色隨機 + 默認新建便籤顏色 + 列表排序 + 最近修改優先 + 最早修改優先 + 按標題排序 + 刪除前確認 + 記住上次打開的文件夾 + 黃色 + 藍色 + 白色 + 綠色 + 紅色 + 通話便籤 + 請輸入名稱 + + 搜索便籤 + 寫點什麼… + 每行一項… + 這裡還沒有內容 + 還沒有通話便籤 + + 共 %1$d 項內容 + + + 共 %1$d 條通話便籤 + + 輕觸底部按鈕即可新建便簽 + 使用上方搜尋可更快找到內容 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..92aa1bd8 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,125 @@ + + + + + + + Notes + Notes 2x2 + Notes 4x4 + No associated note found, click to create associated note. + + + + Add note + Got it + Take a look + + + New Folder + Settings + Back + More + + + Enabled + Disabled + + + Delete + + %d selected + %d selected + + Nothing selected, the operation is invalid + Enter check list + Leave check list + + + Small + Medium + Large + Super + + + Delete folder + Change folder name + The folder %1$s exist, please rename + + + Share + Share note + Export as TXT + Save as Image + Send to home + Remind me + Delete reminder + Note added to home + + + Confirm to delete folder and its notes? + Delete selected notes + + Confirm to delete the selected %d note? + Confirm to delete the selected %d notes? + + Confirm to delete this note? + + + Export failed, please check SD card + No app available to share this note + Save image failed + The note is not exist + Cannot export an empty note + Sorry, can not set clock on empty note + Sorry, can not send and empty note to home + Export successful + Image saved to gallery + + + Settings + New note background color random + Default new note color + List sorting + Newest updated first + Oldest updated first + Title A-Z + Confirm before delete + Remember last folder + + + Yellow + Blue + White + Green + Red + + + Call notes + Input name + + + Search notes + Write something… + One item per line… + + + Nothing here yet + No call notes yet + + %1$d item here + %1$d items here + + + %1$d call note + %1$d call notes + + Tap the bottom button to add a note + Use search above to find notes faster + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..1c267898 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,33 @@ + + + + + + + + + - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml deleted file mode 100644 index fe58f8f6..00000000 --- a/res/xml/preferences.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - diff --git a/res/xml/searchable.xml b/res/xml/searchable.xml deleted file mode 100644 index bf74f14a..00000000 --- a/res/xml/searchable.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..8cc109d1 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,24 @@ +import org.gradle.api.initialization.resolve.RepositoriesMode + +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "Notes" +include(":app") diff --git a/src/net/micode/notes/data/Contact.java b/src/net/micode/notes/data/Contact.java deleted file mode 100644 index d97ac5d7..00000000 --- a/src/net/micode/notes/data/Contact.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.data; - -import android.content.Context; -import android.database.Cursor; -import android.provider.ContactsContract.CommonDataKinds.Phone; -import android.provider.ContactsContract.Data; -import android.telephony.PhoneNumberUtils; -import android.util.Log; - -import java.util.HashMap; - -public class Contact { - private static HashMap sContactCache; - private static final String TAG = "Contact"; - - private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER - + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" - + " AND " + Data.RAW_CONTACT_ID + " IN " - + "(SELECT raw_contact_id " - + " FROM phone_lookup" - + " WHERE min_match = '+')"; - - public static String getContact(Context context, String phoneNumber) { - if(sContactCache == null) { - sContactCache = new HashMap(); - } - - if(sContactCache.containsKey(phoneNumber)) { - return sContactCache.get(phoneNumber); - } - - String selection = CALLER_ID_SELECTION.replace("+", - PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); - Cursor cursor = context.getContentResolver().query( - Data.CONTENT_URI, - new String [] { Phone.DISPLAY_NAME }, - selection, - new String[] { phoneNumber }, - null); - - if (cursor != null && cursor.moveToFirst()) { - try { - String name = cursor.getString(0); - sContactCache.put(phoneNumber, name); - return name; - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, " Cursor get string error " + e.toString()); - return null; - } finally { - cursor.close(); - } - } else { - Log.d(TAG, "No contact matched with number:" + phoneNumber); - return null; - } - } -} diff --git a/src/net/micode/notes/data/Notes.java b/src/net/micode/notes/data/Notes.java deleted file mode 100644 index f240604b..00000000 --- a/src/net/micode/notes/data/Notes.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.data; - -import android.net.Uri; -public class Notes { - public static final String AUTHORITY = "micode_notes"; - public static final String TAG = "Notes"; - public static final int TYPE_NOTE = 0; - public static final int TYPE_FOLDER = 1; - public static final int TYPE_SYSTEM = 2; - - /** - * Following IDs are system folders' identifiers - * {@link Notes#ID_ROOT_FOLDER } is default folder - * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder - * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records - */ - public static final int ID_ROOT_FOLDER = 0; - public static final int ID_TEMPARAY_FOLDER = -1; - public static final int ID_CALL_RECORD_FOLDER = -2; - public static final int ID_TRASH_FOLER = -3; - - public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; - public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; - public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id"; - public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type"; - public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; - public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date"; - - public static final int TYPE_WIDGET_INVALIDE = -1; - public static final int TYPE_WIDGET_2X = 0; - public static final int TYPE_WIDGET_4X = 1; - - public static class DataConstants { - public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; - public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; - } - - /** - * Uri to query all notes and folders - */ - public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); - - /** - * Uri to query data - */ - public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); - - public interface NoteColumns { - /** - * The unique ID for a row - *

Type: INTEGER (long)

- */ - public static final String ID = "_id"; - - /** - * The parent's id for note or folder - *

Type: INTEGER (long)

- */ - public static final String PARENT_ID = "parent_id"; - - /** - * Created data for note or folder - *

Type: INTEGER (long)

- */ - public static final String CREATED_DATE = "created_date"; - - /** - * Latest modified date - *

Type: INTEGER (long)

- */ - public static final String MODIFIED_DATE = "modified_date"; - - - /** - * Alert date - *

Type: INTEGER (long)

- */ - public static final String ALERTED_DATE = "alert_date"; - - /** - * Folder's name or text content of note - *

Type: TEXT

- */ - public static final String SNIPPET = "snippet"; - - /** - * Note's widget id - *

Type: INTEGER (long)

- */ - public static final String WIDGET_ID = "widget_id"; - - /** - * Note's widget type - *

Type: INTEGER (long)

- */ - public static final String WIDGET_TYPE = "widget_type"; - - /** - * Note's background color's id - *

Type: INTEGER (long)

- */ - public static final String BG_COLOR_ID = "bg_color_id"; - - /** - * For text note, it doesn't has attachment, for multi-media - * note, it has at least one attachment - *

Type: INTEGER

- */ - public static final String HAS_ATTACHMENT = "has_attachment"; - - /** - * Folder's count of notes - *

Type: INTEGER (long)

- */ - public static final String NOTES_COUNT = "notes_count"; - - /** - * The file type: folder or note - *

Type: INTEGER

- */ - public static final String TYPE = "type"; - - /** - * The last sync id - *

Type: INTEGER (long)

- */ - public static final String SYNC_ID = "sync_id"; - - /** - * Sign to indicate local modified or not - *

Type: INTEGER

- */ - public static final String LOCAL_MODIFIED = "local_modified"; - - /** - * Original parent id before moving into temporary folder - *

Type : INTEGER

- */ - public static final String ORIGIN_PARENT_ID = "origin_parent_id"; - - /** - * The gtask id - *

Type : TEXT

- */ - public static final String GTASK_ID = "gtask_id"; - - /** - * The version code - *

Type : INTEGER (long)

- */ - public static final String VERSION = "version"; - } - - public interface DataColumns { - /** - * The unique ID for a row - *

Type: INTEGER (long)

- */ - public static final String ID = "_id"; - - /** - * The MIME type of the item represented by this row. - *

Type: Text

- */ - public static final String MIME_TYPE = "mime_type"; - - /** - * The reference id to note that this data belongs to - *

Type: INTEGER (long)

- */ - public static final String NOTE_ID = "note_id"; - - /** - * Created data for note or folder - *

Type: INTEGER (long)

- */ - public static final String CREATED_DATE = "created_date"; - - /** - * Latest modified date - *

Type: INTEGER (long)

- */ - public static final String MODIFIED_DATE = "modified_date"; - - /** - * Data's content - *

Type: TEXT

- */ - public static final String CONTENT = "content"; - - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

- */ - public static final String DATA1 = "data1"; - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * integer data type - *

Type: INTEGER

- */ - public static final String DATA2 = "data2"; - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

- */ - public static final String DATA3 = "data3"; - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

- */ - public static final String DATA4 = "data4"; - - /** - * Generic data column, the meaning is {@link #MIMETYPE} specific, used for - * TEXT data type - *

Type: TEXT

- */ - public static final String DATA5 = "data5"; - } - - public static final class TextNote implements DataColumns { - /** - * Mode to indicate the text in check list mode or not - *

Type: Integer 1:check list mode 0: normal mode

- */ - public static final String MODE = DATA1; - - public static final int MODE_CHECK_LIST = 1; - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); - } - - public static final class CallNote implements DataColumns { - /** - * Call date for this record - *

Type: INTEGER (long)

- */ - public static final String CALL_DATE = DATA1; - - /** - * Phone number for this record - *

Type: TEXT

- */ - public static final String PHONE_NUMBER = DATA3; - - public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; - - public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; - - public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); - } -} diff --git a/src/net/micode/notes/data/NotesDatabaseHelper.java b/src/net/micode/notes/data/NotesDatabaseHelper.java deleted file mode 100644 index ffe5d577..00000000 --- a/src/net/micode/notes/data/NotesDatabaseHelper.java +++ /dev/null @@ -1,362 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.data; - -import android.content.ContentValues; -import android.content.Context; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.util.Log; - -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.DataConstants; -import net.micode.notes.data.Notes.NoteColumns; - - -public class NotesDatabaseHelper extends SQLiteOpenHelper { - private static final String DB_NAME = "note.db"; - - private static final int DB_VERSION = 4; - - public interface TABLE { - public static final String NOTE = "note"; - - public static final String DATA = "data"; - } - - private static final String TAG = "NotesDatabaseHelper"; - - private static NotesDatabaseHelper mInstance; - - private static final String CREATE_NOTE_TABLE_SQL = - "CREATE TABLE " + TABLE.NOTE + "(" + - NoteColumns.ID + " INTEGER PRIMARY KEY," + - NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + - NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + - ")"; - - private static final String CREATE_DATA_TABLE_SQL = - "CREATE TABLE " + TABLE.DATA + "(" + - DataColumns.ID + " INTEGER PRIMARY KEY," + - DataColumns.MIME_TYPE + " TEXT NOT NULL," + - DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA1 + " INTEGER," + - DataColumns.DATA2 + " INTEGER," + - DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + - ")"; - - private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = - "CREATE INDEX IF NOT EXISTS note_id_index ON " + - TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; - - /** - * Increase folder's note count when move note to the folder - */ - private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_update "+ - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; - - /** - * Decrease folder's note count when move note from folder - */ - private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_update " + - " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + - " END"; - - /** - * Increase folder's note count when insert new note to the folder - */ - private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = - "CREATE TRIGGER increase_folder_count_on_insert " + - " AFTER INSERT ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + - " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + - " END"; - - /** - * Decrease folder's note count when delete note from the folder - */ - private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = - "CREATE TRIGGER decrease_folder_count_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN " + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + - " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0;" + - " END"; - - /** - * Update note's content when insert data with type {@link DataConstants#NOTE} - */ - private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = - "CREATE TRIGGER update_note_content_on_insert " + - " AFTER INSERT ON " + TABLE.DATA + - " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; - - /** - * Update note's content when data with {@link DataConstants#NOTE} type has changed - */ - private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = - "CREATE TRIGGER update_note_content_on_update " + - " AFTER UPDATE ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + - " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + - " END"; - - /** - * Update note's content when data with {@link DataConstants#NOTE} type has deleted - */ - private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = - "CREATE TRIGGER update_note_content_on_delete " + - " AFTER delete ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=''" + - " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + - " END"; - - /** - * Delete datas belong to note which has been deleted - */ - private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = - "CREATE TRIGGER delete_data_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.DATA + - " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + - " END"; - - /** - * Delete notes belong to folder which has been deleted - */ - private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = - "CREATE TRIGGER folder_delete_notes_on_delete " + - " AFTER DELETE ON " + TABLE.NOTE + - " BEGIN" + - " DELETE FROM " + TABLE.NOTE + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; - - /** - * Move notes belong to folder which has been moved to trash folder - */ - private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = - "CREATE TRIGGER folder_move_notes_on_trash " + - " AFTER UPDATE ON " + TABLE.NOTE + - " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " BEGIN" + - " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + - " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + - " END"; - - public NotesDatabaseHelper(Context context) { - super(context, DB_NAME, null, DB_VERSION); - } - - public void createNoteTable(SQLiteDatabase db) { - db.execSQL(CREATE_NOTE_TABLE_SQL); - reCreateNoteTableTriggers(db); - createSystemFolder(db); - Log.d(TAG, "note table has been created"); - } - - private void reCreateNoteTableTriggers(SQLiteDatabase db) { - db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); - db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); - db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); - db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); - - db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); - db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); - db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); - db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); - db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); - db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); - db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); - } - - private void createSystemFolder(SQLiteDatabase db) { - ContentValues values = new ContentValues(); - - /** - * call record foler for call notes - */ - values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - - /** - * root folder which is default folder - */ - values.clear(); - values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - - /** - * temporary folder which is used for moving note - */ - values.clear(); - values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - - /** - * create trash folder - */ - values.clear(); - values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - } - - public void createDataTable(SQLiteDatabase db) { - db.execSQL(CREATE_DATA_TABLE_SQL); - reCreateDataTableTriggers(db); - db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); - Log.d(TAG, "data table has been created"); - } - - private void reCreateDataTableTriggers(SQLiteDatabase db) { - db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); - - db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); - db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); - db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); - } - - static synchronized NotesDatabaseHelper getInstance(Context context) { - if (mInstance == null) { - mInstance = new NotesDatabaseHelper(context); - } - return mInstance; - } - - @Override - public void onCreate(SQLiteDatabase db) { - createNoteTable(db); - createDataTable(db); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - boolean reCreateTriggers = false; - boolean skipV2 = false; - - if (oldVersion == 1) { - upgradeToV2(db); - skipV2 = true; // this upgrade including the upgrade from v2 to v3 - oldVersion++; - } - - if (oldVersion == 2 && !skipV2) { - upgradeToV3(db); - reCreateTriggers = true; - oldVersion++; - } - - if (oldVersion == 3) { - upgradeToV4(db); - oldVersion++; - } - - if (reCreateTriggers) { - reCreateNoteTableTriggers(db); - reCreateDataTableTriggers(db); - } - - if (oldVersion != newVersion) { - throw new IllegalStateException("Upgrade notes database to version " + newVersion - + "fails"); - } - } - - private void upgradeToV2(SQLiteDatabase db) { - db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); - db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); - createNoteTable(db); - createDataTable(db); - } - - private void upgradeToV3(SQLiteDatabase db) { - // drop unused triggers - db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); - // add a column for gtask id - db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID - + " TEXT NOT NULL DEFAULT ''"); - // add a trash system folder - ContentValues values = new ContentValues(); - values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); - } - - private void upgradeToV4(SQLiteDatabase db) { - db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION - + " INTEGER NOT NULL DEFAULT 0"); - } -} diff --git a/src/net/micode/notes/data/NotesProvider.java b/src/net/micode/notes/data/NotesProvider.java deleted file mode 100644 index edb0a606..00000000 --- a/src/net/micode/notes/data/NotesProvider.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.data; - - -import android.app.SearchManager; -import android.content.ContentProvider; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Intent; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.net.Uri; -import android.text.TextUtils; -import android.util.Log; - -import net.micode.notes.R; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.NotesDatabaseHelper.TABLE; - - -public class NotesProvider extends ContentProvider { - private static final UriMatcher mMatcher; - - private NotesDatabaseHelper mHelper; - - private static final String TAG = "NotesProvider"; - - private static final int URI_NOTE = 1; - private static final int URI_NOTE_ITEM = 2; - private static final int URI_DATA = 3; - private static final int URI_DATA_ITEM = 4; - - private static final int URI_SEARCH = 5; - private static final int URI_SEARCH_SUGGEST = 6; - - static { - mMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); - mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); - mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); - mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); - mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); - } - - /** - * x'0A' represents the '\n' character in sqlite. For title and content in the search result, - * we will trim '\n' and white space in order to show more information. - */ - private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," - + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," - + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," - + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," - + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," - + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; - - private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION - + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" - + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER - + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; - - @Override - public boolean onCreate() { - mHelper = NotesDatabaseHelper.getInstance(getContext()); - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - Cursor c = null; - SQLiteDatabase db = mHelper.getReadableDatabase(); - String id = null; - switch (mMatcher.match(uri)) { - case URI_NOTE: - c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, - sortOrder); - break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id - + parseSelection(selection), selectionArgs, null, null, sortOrder); - break; - case URI_DATA: - c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, - sortOrder); - break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id - + parseSelection(selection), selectionArgs, null, null, sortOrder); - break; - case URI_SEARCH: - case URI_SEARCH_SUGGEST: - if (sortOrder != null || projection != null) { - throw new IllegalArgumentException( - "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); - } - - String searchString = null; - if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { - if (uri.getPathSegments().size() > 1) { - searchString = uri.getPathSegments().get(1); - } - } else { - searchString = uri.getQueryParameter("pattern"); - } - - if (TextUtils.isEmpty(searchString)) { - return null; - } - - try { - searchString = String.format("%%%s%%", searchString); - c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, - new String[] { searchString }); - } catch (IllegalStateException ex) { - Log.e(TAG, "got exception: " + ex.toString()); - } - break; - default: - throw new IllegalArgumentException("Unknown URI " + uri); - } - if (c != null) { - c.setNotificationUri(getContext().getContentResolver(), uri); - } - return c; - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - SQLiteDatabase db = mHelper.getWritableDatabase(); - long dataId = 0, noteId = 0, insertedId = 0; - switch (mMatcher.match(uri)) { - case URI_NOTE: - insertedId = noteId = db.insert(TABLE.NOTE, null, values); - break; - case URI_DATA: - if (values.containsKey(DataColumns.NOTE_ID)) { - noteId = values.getAsLong(DataColumns.NOTE_ID); - } else { - Log.d(TAG, "Wrong data format without note id:" + values.toString()); - } - insertedId = dataId = db.insert(TABLE.DATA, null, values); - break; - default: - throw new IllegalArgumentException("Unknown URI " + uri); - } - // Notify the note uri - if (noteId > 0) { - getContext().getContentResolver().notifyChange( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); - } - - // Notify the data uri - if (dataId > 0) { - getContext().getContentResolver().notifyChange( - ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); - } - - return ContentUris.withAppendedId(uri, insertedId); - } - - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - int count = 0; - String id = null; - SQLiteDatabase db = mHelper.getWritableDatabase(); - boolean deleteData = false; - switch (mMatcher.match(uri)) { - case URI_NOTE: - selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; - count = db.delete(TABLE.NOTE, selection, selectionArgs); - break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - /** - * ID that smaller than 0 is system folder which is not allowed to - * trash - */ - long noteId = Long.valueOf(id); - if (noteId <= 0) { - break; - } - count = db.delete(TABLE.NOTE, - NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); - break; - case URI_DATA: - count = db.delete(TABLE.DATA, selection, selectionArgs); - deleteData = true; - break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - count = db.delete(TABLE.DATA, - DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); - deleteData = true; - break; - default: - throw new IllegalArgumentException("Unknown URI " + uri); - } - if (count > 0) { - if (deleteData) { - getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); - } - getContext().getContentResolver().notifyChange(uri, null); - } - return count; - } - - @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - int count = 0; - String id = null; - SQLiteDatabase db = mHelper.getWritableDatabase(); - boolean updateData = false; - switch (mMatcher.match(uri)) { - case URI_NOTE: - increaseNoteVersion(-1, selection, selectionArgs); - count = db.update(TABLE.NOTE, values, selection, selectionArgs); - break; - case URI_NOTE_ITEM: - id = uri.getPathSegments().get(1); - increaseNoteVersion(Long.valueOf(id), selection, selectionArgs); - count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id - + parseSelection(selection), selectionArgs); - break; - case URI_DATA: - count = db.update(TABLE.DATA, values, selection, selectionArgs); - updateData = true; - break; - case URI_DATA_ITEM: - id = uri.getPathSegments().get(1); - count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id - + parseSelection(selection), selectionArgs); - updateData = true; - break; - default: - throw new IllegalArgumentException("Unknown URI " + uri); - } - - if (count > 0) { - if (updateData) { - getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); - } - getContext().getContentResolver().notifyChange(uri, null); - } - return count; - } - - private String parseSelection(String selection) { - return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); - } - - private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { - StringBuilder sql = new StringBuilder(120); - sql.append("UPDATE "); - sql.append(TABLE.NOTE); - sql.append(" SET "); - sql.append(NoteColumns.VERSION); - sql.append("=" + NoteColumns.VERSION + "+1 "); - - if (id > 0 || !TextUtils.isEmpty(selection)) { - sql.append(" WHERE "); - } - if (id > 0) { - sql.append(NoteColumns.ID + "=" + String.valueOf(id)); - } - if (!TextUtils.isEmpty(selection)) { - String selectString = id > 0 ? parseSelection(selection) : selection; - for (String args : selectionArgs) { - selectString = selectString.replaceFirst("\\?", args); - } - sql.append(selectString); - } - - mHelper.getWritableDatabase().execSQL(sql.toString()); - } - - @Override - public String getType(Uri uri) { - // TODO Auto-generated method stub - return null; - } - -} diff --git a/src/net/micode/notes/gtask/data/MetaData.java b/src/net/micode/notes/gtask/data/MetaData.java deleted file mode 100644 index 3a2050b0..00000000 --- a/src/net/micode/notes/gtask/data/MetaData.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.database.Cursor; -import android.util.Log; - -import net.micode.notes.tool.GTaskStringUtils; - -import org.json.JSONException; -import org.json.JSONObject; - - -public class MetaData extends Task { - private final static String TAG = MetaData.class.getSimpleName(); - - private String mRelatedGid = null; - - public void setMeta(String gid, JSONObject metaInfo) { - try { - metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); - } catch (JSONException e) { - Log.e(TAG, "failed to put related gid"); - } - setNotes(metaInfo.toString()); - setName(GTaskStringUtils.META_NOTE_NAME); - } - - public String getRelatedGid() { - return mRelatedGid; - } - - @Override - public boolean isWorthSaving() { - return getNotes() != null; - } - - @Override - public void setContentByRemoteJSON(JSONObject js) { - super.setContentByRemoteJSON(js); - if (getNotes() != null) { - try { - JSONObject metaInfo = new JSONObject(getNotes().trim()); - mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); - } catch (JSONException e) { - Log.w(TAG, "failed to get related gid"); - mRelatedGid = null; - } - } - } - - @Override - public void setContentByLocalJSON(JSONObject js) { - // this function should not be called - throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); - } - - @Override - public JSONObject getLocalJSONFromContent() { - throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); - } - - @Override - public int getSyncAction(Cursor c) { - throw new IllegalAccessError("MetaData:getSyncAction should not be called"); - } - -} diff --git a/src/net/micode/notes/gtask/data/Node.java b/src/net/micode/notes/gtask/data/Node.java deleted file mode 100644 index 63950e0d..00000000 --- a/src/net/micode/notes/gtask/data/Node.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.database.Cursor; - -import org.json.JSONObject; - -public abstract class Node { - public static final int SYNC_ACTION_NONE = 0; - - public static final int SYNC_ACTION_ADD_REMOTE = 1; - - public static final int SYNC_ACTION_ADD_LOCAL = 2; - - public static final int SYNC_ACTION_DEL_REMOTE = 3; - - public static final int SYNC_ACTION_DEL_LOCAL = 4; - - public static final int SYNC_ACTION_UPDATE_REMOTE = 5; - - public static final int SYNC_ACTION_UPDATE_LOCAL = 6; - - public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; - - public static final int SYNC_ACTION_ERROR = 8; - - private String mGid; - - private String mName; - - private long mLastModified; - - private boolean mDeleted; - - public Node() { - mGid = null; - mName = ""; - mLastModified = 0; - mDeleted = false; - } - - public abstract JSONObject getCreateAction(int actionId); - - public abstract JSONObject getUpdateAction(int actionId); - - public abstract void setContentByRemoteJSON(JSONObject js); - - public abstract void setContentByLocalJSON(JSONObject js); - - public abstract JSONObject getLocalJSONFromContent(); - - public abstract int getSyncAction(Cursor c); - - public void setGid(String gid) { - this.mGid = gid; - } - - public void setName(String name) { - this.mName = name; - } - - public void setLastModified(long lastModified) { - this.mLastModified = lastModified; - } - - public void setDeleted(boolean deleted) { - this.mDeleted = deleted; - } - - public String getGid() { - return this.mGid; - } - - public String getName() { - return this.mName; - } - - public long getLastModified() { - return this.mLastModified; - } - - public boolean getDeleted() { - return this.mDeleted; - } - -} diff --git a/src/net/micode/notes/gtask/data/SqlData.java b/src/net/micode/notes/gtask/data/SqlData.java deleted file mode 100644 index d3ec3be1..00000000 --- a/src/net/micode/notes/gtask/data/SqlData.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.DataConstants; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.NotesDatabaseHelper.TABLE; -import net.micode.notes.gtask.exception.ActionFailureException; - -import org.json.JSONException; -import org.json.JSONObject; - - -public class SqlData { - private static final String TAG = SqlData.class.getSimpleName(); - - private static final int INVALID_ID = -99999; - - public static final String[] PROJECTION_DATA = new String[] { - DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, - DataColumns.DATA3 - }; - - public static final int DATA_ID_COLUMN = 0; - - public static final int DATA_MIME_TYPE_COLUMN = 1; - - public static final int DATA_CONTENT_COLUMN = 2; - - public static final int DATA_CONTENT_DATA_1_COLUMN = 3; - - public static final int DATA_CONTENT_DATA_3_COLUMN = 4; - - private ContentResolver mContentResolver; - - private boolean mIsCreate; - - private long mDataId; - - private String mDataMimeType; - - private String mDataContent; - - private long mDataContentData1; - - private String mDataContentData3; - - private ContentValues mDiffDataValues; - - public SqlData(Context context) { - mContentResolver = context.getContentResolver(); - mIsCreate = true; - mDataId = INVALID_ID; - mDataMimeType = DataConstants.NOTE; - mDataContent = ""; - mDataContentData1 = 0; - mDataContentData3 = ""; - mDiffDataValues = new ContentValues(); - } - - public SqlData(Context context, Cursor c) { - mContentResolver = context.getContentResolver(); - mIsCreate = false; - loadFromCursor(c); - mDiffDataValues = new ContentValues(); - } - - private void loadFromCursor(Cursor c) { - mDataId = c.getLong(DATA_ID_COLUMN); - mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); - mDataContent = c.getString(DATA_CONTENT_COLUMN); - mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); - mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); - } - - public void setContent(JSONObject js) throws JSONException { - long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; - if (mIsCreate || mDataId != dataId) { - mDiffDataValues.put(DataColumns.ID, dataId); - } - mDataId = dataId; - - String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE) - : DataConstants.NOTE; - if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { - mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); - } - mDataMimeType = dataMimeType; - - String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; - if (mIsCreate || !mDataContent.equals(dataContent)) { - mDiffDataValues.put(DataColumns.CONTENT, dataContent); - } - mDataContent = dataContent; - - long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; - if (mIsCreate || mDataContentData1 != dataContentData1) { - mDiffDataValues.put(DataColumns.DATA1, dataContentData1); - } - mDataContentData1 = dataContentData1; - - String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; - if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { - mDiffDataValues.put(DataColumns.DATA3, dataContentData3); - } - mDataContentData3 = dataContentData3; - } - - public JSONObject getContent() throws JSONException { - if (mIsCreate) { - Log.e(TAG, "it seems that we haven't created this in database yet"); - return null; - } - JSONObject js = new JSONObject(); - js.put(DataColumns.ID, mDataId); - js.put(DataColumns.MIME_TYPE, mDataMimeType); - js.put(DataColumns.CONTENT, mDataContent); - js.put(DataColumns.DATA1, mDataContentData1); - js.put(DataColumns.DATA3, mDataContentData3); - return js; - } - - public void commit(long noteId, boolean validateVersion, long version) { - - if (mIsCreate) { - if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { - mDiffDataValues.remove(DataColumns.ID); - } - - mDiffDataValues.put(DataColumns.NOTE_ID, noteId); - Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); - try { - mDataId = Long.valueOf(uri.getPathSegments().get(1)); - } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); - throw new ActionFailureException("create note failed"); - } - } else { - if (mDiffDataValues.size() > 0) { - int result = 0; - if (!validateVersion) { - result = mContentResolver.update(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); - } else { - result = mContentResolver.update(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, - " ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE - + " WHERE " + NoteColumns.VERSION + "=?)", new String[] { - String.valueOf(noteId), String.valueOf(version) - }); - } - if (result == 0) { - Log.w(TAG, "there is no update. maybe user updates note when syncing"); - } - } - } - - mDiffDataValues.clear(); - mIsCreate = false; - } - - public long getId() { - return mDataId; - } -} diff --git a/src/net/micode/notes/gtask/data/SqlNote.java b/src/net/micode/notes/gtask/data/SqlNote.java deleted file mode 100644 index 79a4095e..00000000 --- a/src/net/micode/notes/gtask/data/SqlNote.java +++ /dev/null @@ -1,505 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.appwidget.AppWidgetManager; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.gtask.exception.ActionFailureException; -import net.micode.notes.tool.GTaskStringUtils; -import net.micode.notes.tool.ResourceParser; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; - - -public class SqlNote { - private static final String TAG = SqlNote.class.getSimpleName(); - - private static final int INVALID_ID = -99999; - - public static final String[] PROJECTION_NOTE = new String[] { - NoteColumns.ID, NoteColumns.ALERTED_DATE, NoteColumns.BG_COLOR_ID, - NoteColumns.CREATED_DATE, NoteColumns.HAS_ATTACHMENT, NoteColumns.MODIFIED_DATE, - NoteColumns.NOTES_COUNT, NoteColumns.PARENT_ID, NoteColumns.SNIPPET, NoteColumns.TYPE, - NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE, NoteColumns.SYNC_ID, - NoteColumns.LOCAL_MODIFIED, NoteColumns.ORIGIN_PARENT_ID, NoteColumns.GTASK_ID, - NoteColumns.VERSION - }; - - public static final int ID_COLUMN = 0; - - public static final int ALERTED_DATE_COLUMN = 1; - - public static final int BG_COLOR_ID_COLUMN = 2; - - public static final int CREATED_DATE_COLUMN = 3; - - public static final int HAS_ATTACHMENT_COLUMN = 4; - - public static final int MODIFIED_DATE_COLUMN = 5; - - public static final int NOTES_COUNT_COLUMN = 6; - - public static final int PARENT_ID_COLUMN = 7; - - public static final int SNIPPET_COLUMN = 8; - - public static final int TYPE_COLUMN = 9; - - public static final int WIDGET_ID_COLUMN = 10; - - public static final int WIDGET_TYPE_COLUMN = 11; - - public static final int SYNC_ID_COLUMN = 12; - - public static final int LOCAL_MODIFIED_COLUMN = 13; - - public static final int ORIGIN_PARENT_ID_COLUMN = 14; - - public static final int GTASK_ID_COLUMN = 15; - - public static final int VERSION_COLUMN = 16; - - private Context mContext; - - private ContentResolver mContentResolver; - - private boolean mIsCreate; - - private long mId; - - private long mAlertDate; - - private int mBgColorId; - - private long mCreatedDate; - - private int mHasAttachment; - - private long mModifiedDate; - - private long mParentId; - - private String mSnippet; - - private int mType; - - private int mWidgetId; - - private int mWidgetType; - - private long mOriginParent; - - private long mVersion; - - private ContentValues mDiffNoteValues; - - private ArrayList mDataList; - - public SqlNote(Context context) { - mContext = context; - mContentResolver = context.getContentResolver(); - mIsCreate = true; - mId = INVALID_ID; - mAlertDate = 0; - mBgColorId = ResourceParser.getDefaultBgId(context); - mCreatedDate = System.currentTimeMillis(); - mHasAttachment = 0; - mModifiedDate = System.currentTimeMillis(); - mParentId = 0; - mSnippet = ""; - mType = Notes.TYPE_NOTE; - mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; - mWidgetType = Notes.TYPE_WIDGET_INVALIDE; - mOriginParent = 0; - mVersion = 0; - mDiffNoteValues = new ContentValues(); - mDataList = new ArrayList(); - } - - public SqlNote(Context context, Cursor c) { - mContext = context; - mContentResolver = context.getContentResolver(); - mIsCreate = false; - loadFromCursor(c); - mDataList = new ArrayList(); - if (mType == Notes.TYPE_NOTE) - loadDataContent(); - mDiffNoteValues = new ContentValues(); - } - - public SqlNote(Context context, long id) { - mContext = context; - mContentResolver = context.getContentResolver(); - mIsCreate = false; - loadFromCursor(id); - mDataList = new ArrayList(); - if (mType == Notes.TYPE_NOTE) - loadDataContent(); - mDiffNoteValues = new ContentValues(); - - } - - private void loadFromCursor(long id) { - Cursor c = null; - try { - c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)", - new String[] { - String.valueOf(id) - }, null); - if (c != null) { - c.moveToNext(); - loadFromCursor(c); - } else { - Log.w(TAG, "loadFromCursor: cursor = null"); - } - } finally { - if (c != null) - c.close(); - } - } - - private void loadFromCursor(Cursor c) { - mId = c.getLong(ID_COLUMN); - mAlertDate = c.getLong(ALERTED_DATE_COLUMN); - mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); - mCreatedDate = c.getLong(CREATED_DATE_COLUMN); - mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); - mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); - mParentId = c.getLong(PARENT_ID_COLUMN); - mSnippet = c.getString(SNIPPET_COLUMN); - mType = c.getInt(TYPE_COLUMN); - mWidgetId = c.getInt(WIDGET_ID_COLUMN); - mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); - mVersion = c.getLong(VERSION_COLUMN); - } - - private void loadDataContent() { - Cursor c = null; - mDataList.clear(); - try { - c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, - "(note_id=?)", new String[] { - String.valueOf(mId) - }, null); - if (c != null) { - if (c.getCount() == 0) { - Log.w(TAG, "it seems that the note has not data"); - return; - } - while (c.moveToNext()) { - SqlData data = new SqlData(mContext, c); - mDataList.add(data); - } - } else { - Log.w(TAG, "loadDataContent: cursor = null"); - } - } finally { - if (c != null) - c.close(); - } - } - - public boolean setContent(JSONObject js) { - try { - JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { - Log.w(TAG, "cannot set system folder"); - } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - // for folder we can only update the snnipet and type - String snippet = note.has(NoteColumns.SNIPPET) ? note - .getString(NoteColumns.SNIPPET) : ""; - if (mIsCreate || !mSnippet.equals(snippet)) { - mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); - } - mSnippet = snippet; - - int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) - : Notes.TYPE_NOTE; - if (mIsCreate || mType != type) { - mDiffNoteValues.put(NoteColumns.TYPE, type); - } - mType = type; - } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { - JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; - if (mIsCreate || mId != id) { - mDiffNoteValues.put(NoteColumns.ID, id); - } - mId = id; - - long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note - .getLong(NoteColumns.ALERTED_DATE) : 0; - if (mIsCreate || mAlertDate != alertDate) { - mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); - } - mAlertDate = alertDate; - - int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note - .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); - if (mIsCreate || mBgColorId != bgColorId) { - mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); - } - mBgColorId = bgColorId; - - long createDate = note.has(NoteColumns.CREATED_DATE) ? note - .getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); - if (mIsCreate || mCreatedDate != createDate) { - mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); - } - mCreatedDate = createDate; - - int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note - .getInt(NoteColumns.HAS_ATTACHMENT) : 0; - if (mIsCreate || mHasAttachment != hasAttachment) { - mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); - } - mHasAttachment = hasAttachment; - - long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note - .getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); - if (mIsCreate || mModifiedDate != modifiedDate) { - mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); - } - mModifiedDate = modifiedDate; - - long parentId = note.has(NoteColumns.PARENT_ID) ? note - .getLong(NoteColumns.PARENT_ID) : 0; - if (mIsCreate || mParentId != parentId) { - mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); - } - mParentId = parentId; - - String snippet = note.has(NoteColumns.SNIPPET) ? note - .getString(NoteColumns.SNIPPET) : ""; - if (mIsCreate || !mSnippet.equals(snippet)) { - mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); - } - mSnippet = snippet; - - int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) - : Notes.TYPE_NOTE; - if (mIsCreate || mType != type) { - mDiffNoteValues.put(NoteColumns.TYPE, type); - } - mType = type; - - int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID) - : AppWidgetManager.INVALID_APPWIDGET_ID; - if (mIsCreate || mWidgetId != widgetId) { - mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); - } - mWidgetId = widgetId; - - int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note - .getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; - if (mIsCreate || mWidgetType != widgetType) { - mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); - } - mWidgetType = widgetType; - - long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note - .getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; - if (mIsCreate || mOriginParent != originParent) { - mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); - } - mOriginParent = originParent; - - for (int i = 0; i < dataArray.length(); i++) { - JSONObject data = dataArray.getJSONObject(i); - SqlData sqlData = null; - if (data.has(DataColumns.ID)) { - long dataId = data.getLong(DataColumns.ID); - for (SqlData temp : mDataList) { - if (dataId == temp.getId()) { - sqlData = temp; - } - } - } - - if (sqlData == null) { - sqlData = new SqlData(mContext); - mDataList.add(sqlData); - } - - sqlData.setContent(data); - } - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - return false; - } - return true; - } - - public JSONObject getContent() { - try { - JSONObject js = new JSONObject(); - - if (mIsCreate) { - Log.e(TAG, "it seems that we haven't created this in database yet"); - return null; - } - - JSONObject note = new JSONObject(); - if (mType == Notes.TYPE_NOTE) { - note.put(NoteColumns.ID, mId); - note.put(NoteColumns.ALERTED_DATE, mAlertDate); - note.put(NoteColumns.BG_COLOR_ID, mBgColorId); - note.put(NoteColumns.CREATED_DATE, mCreatedDate); - note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment); - note.put(NoteColumns.MODIFIED_DATE, mModifiedDate); - note.put(NoteColumns.PARENT_ID, mParentId); - note.put(NoteColumns.SNIPPET, mSnippet); - note.put(NoteColumns.TYPE, mType); - note.put(NoteColumns.WIDGET_ID, mWidgetId); - note.put(NoteColumns.WIDGET_TYPE, mWidgetType); - note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); - js.put(GTaskStringUtils.META_HEAD_NOTE, note); - - JSONArray dataArray = new JSONArray(); - for (SqlData sqlData : mDataList) { - JSONObject data = sqlData.getContent(); - if (data != null) { - dataArray.put(data); - } - } - js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); - } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { - note.put(NoteColumns.ID, mId); - note.put(NoteColumns.TYPE, mType); - note.put(NoteColumns.SNIPPET, mSnippet); - js.put(GTaskStringUtils.META_HEAD_NOTE, note); - } - - return js; - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - return null; - } - - public void setParentId(long id) { - mParentId = id; - mDiffNoteValues.put(NoteColumns.PARENT_ID, id); - } - - public void setGtaskId(String gid) { - mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); - } - - public void setSyncId(long syncId) { - mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); - } - - public void resetLocalModified() { - mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); - } - - public long getId() { - return mId; - } - - public long getParentId() { - return mParentId; - } - - public String getSnippet() { - return mSnippet; - } - - public boolean isNoteType() { - return mType == Notes.TYPE_NOTE; - } - - public void commit(boolean validateVersion) { - if (mIsCreate) { - if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { - mDiffNoteValues.remove(NoteColumns.ID); - } - - Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); - try { - mId = Long.valueOf(uri.getPathSegments().get(1)); - } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); - throw new ActionFailureException("create note failed"); - } - if (mId == 0) { - throw new IllegalStateException("Create thread id failed"); - } - - if (mType == Notes.TYPE_NOTE) { - for (SqlData sqlData : mDataList) { - sqlData.commit(mId, false, -1); - } - } - } else { - if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { - Log.e(TAG, "No such note"); - throw new IllegalStateException("Try to update note with invalid id"); - } - if (mDiffNoteValues.size() > 0) { - mVersion ++; - int result = 0; - if (!validateVersion) { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?)", new String[] { - String.valueOf(mId) - }); - } else { - result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" - + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", - new String[] { - String.valueOf(mId), String.valueOf(mVersion) - }); - } - if (result == 0) { - Log.w(TAG, "there is no update. maybe user updates note when syncing"); - } - } - - if (mType == Notes.TYPE_NOTE) { - for (SqlData sqlData : mDataList) { - sqlData.commit(mId, validateVersion, mVersion); - } - } - } - - // refresh local info - loadFromCursor(mId); - if (mType == Notes.TYPE_NOTE) - loadDataContent(); - - mDiffNoteValues.clear(); - mIsCreate = false; - } -} diff --git a/src/net/micode/notes/gtask/data/Task.java b/src/net/micode/notes/gtask/data/Task.java deleted file mode 100644 index 6a19454d..00000000 --- a/src/net/micode/notes/gtask/data/Task.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.database.Cursor; -import android.text.TextUtils; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.DataConstants; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.gtask.exception.ActionFailureException; -import net.micode.notes.tool.GTaskStringUtils; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - - -public class Task extends Node { - private static final String TAG = Task.class.getSimpleName(); - - private boolean mCompleted; - - private String mNotes; - - private JSONObject mMetaInfo; - - private Task mPriorSibling; - - private TaskList mParent; - - public Task() { - super(); - mCompleted = false; - mNotes = null; - mPriorSibling = null; - mParent = null; - mMetaInfo = null; - } - - public JSONObject getCreateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // index - js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); - entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_TASK); - if (getNotes() != null) { - entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); - } - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - - // parent_id - js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid()); - - // dest_parent_type - js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - - // list_id - js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid()); - - // prior_sibling_id - if (mPriorSibling != null) { - js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid()); - } - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate task-create jsonobject"); - } - - return js; - } - - public JSONObject getUpdateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // id - js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - if (getNotes() != null) { - entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); - } - entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate task-update jsonobject"); - } - - return js; - } - - public void setContentByRemoteJSON(JSONObject js) { - if (js != null) { - try { - // id - if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { - setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); - } - - // last_modified - if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { - setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); - } - - // name - if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { - setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); - } - - // notes - if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { - setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); - } - - // deleted - if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { - setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); - } - - // completed - if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { - setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to get task content from jsonobject"); - } - } - } - - public void setContentByLocalJSON(JSONObject js) { - if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) - || !js.has(GTaskStringUtils.META_HEAD_DATA)) { - Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); - } - - try { - JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - - if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) { - Log.e(TAG, "invalid type"); - return; - } - - for (int i = 0; i < dataArray.length(); i++) { - JSONObject data = dataArray.getJSONObject(i); - if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { - setName(data.getString(DataColumns.CONTENT)); - break; - } - } - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - } - - public JSONObject getLocalJSONFromContent() { - String name = getName(); - try { - if (mMetaInfo == null) { - // new task created from web - if (name == null) { - Log.w(TAG, "the note seems to be an empty one"); - return null; - } - - JSONObject js = new JSONObject(); - JSONObject note = new JSONObject(); - JSONArray dataArray = new JSONArray(); - JSONObject data = new JSONObject(); - data.put(DataColumns.CONTENT, name); - dataArray.put(data); - js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); - note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); - js.put(GTaskStringUtils.META_HEAD_NOTE, note); - return js; - } else { - // synced task - JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - - for (int i = 0; i < dataArray.length(); i++) { - JSONObject data = dataArray.getJSONObject(i); - if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { - data.put(DataColumns.CONTENT, getName()); - break; - } - } - - note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); - return mMetaInfo; - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - return null; - } - } - - public void setMetaInfo(MetaData metaData) { - if (metaData != null && metaData.getNotes() != null) { - try { - mMetaInfo = new JSONObject(metaData.getNotes()); - } catch (JSONException e) { - Log.w(TAG, e.toString()); - mMetaInfo = null; - } - } - } - - public int getSyncAction(Cursor c) { - try { - JSONObject noteInfo = null; - if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { - noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - } - - if (noteInfo == null) { - Log.w(TAG, "it seems that note meta has been deleted"); - return SYNC_ACTION_UPDATE_REMOTE; - } - - if (!noteInfo.has(NoteColumns.ID)) { - Log.w(TAG, "remote note id seems to be deleted"); - return SYNC_ACTION_UPDATE_LOCAL; - } - - // validate the note id now - if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { - Log.w(TAG, "note id doesn't match"); - return SYNC_ACTION_UPDATE_LOCAL; - } - - if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side - return SYNC_ACTION_NONE; - } else { - // apply remote to local - return SYNC_ACTION_UPDATE_LOCAL; - } - } else { - // validate gtask id - if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { - Log.e(TAG, "gtask id doesn't match"); - return SYNC_ACTION_ERROR; - } - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only - return SYNC_ACTION_UPDATE_REMOTE; - } else { - return SYNC_ACTION_UPDATE_CONFLICT; - } - } - } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - - return SYNC_ACTION_ERROR; - } - - public boolean isWorthSaving() { - return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) - || (getNotes() != null && getNotes().trim().length() > 0); - } - - public void setCompleted(boolean completed) { - this.mCompleted = completed; - } - - public void setNotes(String notes) { - this.mNotes = notes; - } - - public void setPriorSibling(Task priorSibling) { - this.mPriorSibling = priorSibling; - } - - public void setParent(TaskList parent) { - this.mParent = parent; - } - - public boolean getCompleted() { - return this.mCompleted; - } - - public String getNotes() { - return this.mNotes; - } - - public Task getPriorSibling() { - return this.mPriorSibling; - } - - public TaskList getParent() { - return this.mParent; - } - -} diff --git a/src/net/micode/notes/gtask/data/TaskList.java b/src/net/micode/notes/gtask/data/TaskList.java deleted file mode 100644 index 4ea21c5b..00000000 --- a/src/net/micode/notes/gtask/data/TaskList.java +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.data; - -import android.database.Cursor; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.gtask.exception.ActionFailureException; -import net.micode.notes.tool.GTaskStringUtils; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; - - -public class TaskList extends Node { - private static final String TAG = TaskList.class.getSimpleName(); - - private int mIndex; - - private ArrayList mChildren; - - public TaskList() { - super(); - mChildren = new ArrayList(); - mIndex = 1; - } - - public JSONObject getCreateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // index - js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); - entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, - GTaskStringUtils.GTASK_JSON_TYPE_GROUP); - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate tasklist-create jsonobject"); - } - - return js; - } - - public JSONObject getUpdateAction(int actionId) { - JSONObject js = new JSONObject(); - - try { - // action_type - js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); - - // action_id - js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); - - // id - js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); - - // entity_delta - JSONObject entity = new JSONObject(); - entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); - entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); - js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to generate tasklist-update jsonobject"); - } - - return js; - } - - public void setContentByRemoteJSON(JSONObject js) { - if (js != null) { - try { - // id - if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { - setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); - } - - // last_modified - if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { - setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); - } - - // name - if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { - setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); - } - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("fail to get tasklist content from jsonobject"); - } - } - } - - public void setContentByLocalJSON(JSONObject js) { - if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { - Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); - } - - try { - JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - - if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { - String name = folder.getString(NoteColumns.SNIPPET); - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); - } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { - if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); - else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) - setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_CALL_NOTE); - else - Log.e(TAG, "invalid system folder"); - } else { - Log.e(TAG, "error type"); - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - } - - public JSONObject getLocalJSONFromContent() { - try { - JSONObject js = new JSONObject(); - JSONObject folder = new JSONObject(); - - String folderName = getName(); - if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) - folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), - folderName.length()); - folder.put(NoteColumns.SNIPPET, folderName); - if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) - || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) - folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - else - folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - - js.put(GTaskStringUtils.META_HEAD_NOTE, folder); - - return js; - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - return null; - } - } - - public int getSyncAction(Cursor c) { - try { - if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { - // there is no local update - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // no update both side - return SYNC_ACTION_NONE; - } else { - // apply remote to local - return SYNC_ACTION_UPDATE_LOCAL; - } - } else { - // validate gtask id - if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { - Log.e(TAG, "gtask id doesn't match"); - return SYNC_ACTION_ERROR; - } - if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { - // local modification only - return SYNC_ACTION_UPDATE_REMOTE; - } else { - // for folder conflicts, just apply local modification - return SYNC_ACTION_UPDATE_REMOTE; - } - } - } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - } - - return SYNC_ACTION_ERROR; - } - - public int getChildTaskCount() { - return mChildren.size(); - } - - public boolean addChildTask(Task task) { - boolean ret = false; - if (task != null && !mChildren.contains(task)) { - ret = mChildren.add(task); - if (ret) { - // need to set prior sibling and parent - task.setPriorSibling(mChildren.isEmpty() ? null : mChildren - .get(mChildren.size() - 1)); - task.setParent(this); - } - } - return ret; - } - - public boolean addChildTask(Task task, int index) { - if (index < 0 || index > mChildren.size()) { - Log.e(TAG, "add child task: invalid index"); - return false; - } - - int pos = mChildren.indexOf(task); - if (task != null && pos == -1) { - mChildren.add(index, task); - - // update the task list - Task preTask = null; - Task afterTask = null; - if (index != 0) - preTask = mChildren.get(index - 1); - if (index != mChildren.size() - 1) - afterTask = mChildren.get(index + 1); - - task.setPriorSibling(preTask); - if (afterTask != null) - afterTask.setPriorSibling(task); - } - - return true; - } - - public boolean removeChildTask(Task task) { - boolean ret = false; - int index = mChildren.indexOf(task); - if (index != -1) { - ret = mChildren.remove(task); - - if (ret) { - // reset prior sibling and parent - task.setPriorSibling(null); - task.setParent(null); - - // update the task list - if (index != mChildren.size()) { - mChildren.get(index).setPriorSibling( - index == 0 ? null : mChildren.get(index - 1)); - } - } - } - return ret; - } - - public boolean moveChildTask(Task task, int index) { - - if (index < 0 || index >= mChildren.size()) { - Log.e(TAG, "move child task: invalid index"); - return false; - } - - int pos = mChildren.indexOf(task); - if (pos == -1) { - Log.e(TAG, "move child task: the task should in the list"); - return false; - } - - if (pos == index) - return true; - return (removeChildTask(task) && addChildTask(task, index)); - } - - public Task findChildTaskByGid(String gid) { - for (int i = 0; i < mChildren.size(); i++) { - Task t = mChildren.get(i); - if (t.getGid().equals(gid)) { - return t; - } - } - return null; - } - - public int getChildTaskIndex(Task task) { - return mChildren.indexOf(task); - } - - public Task getChildTaskByIndex(int index) { - if (index < 0 || index >= mChildren.size()) { - Log.e(TAG, "getTaskByIndex: invalid index"); - return null; - } - return mChildren.get(index); - } - - public Task getChilTaskByGid(String gid) { - for (Task task : mChildren) { - if (task.getGid().equals(gid)) - return task; - } - return null; - } - - public ArrayList getChildTaskList() { - return this.mChildren; - } - - public void setIndex(int index) { - this.mIndex = index; - } - - public int getIndex() { - return this.mIndex; - } -} diff --git a/src/net/micode/notes/gtask/remote/GTaskASyncTask.java b/src/net/micode/notes/gtask/remote/GTaskASyncTask.java deleted file mode 100644 index b3b61e72..00000000 --- a/src/net/micode/notes/gtask/remote/GTaskASyncTask.java +++ /dev/null @@ -1,123 +0,0 @@ - -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.remote; - -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.os.AsyncTask; - -import net.micode.notes.R; -import net.micode.notes.ui.NotesListActivity; -import net.micode.notes.ui.NotesPreferenceActivity; - - -public class GTaskASyncTask extends AsyncTask { - - private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; - - public interface OnCompleteListener { - void onComplete(); - } - - private Context mContext; - - private NotificationManager mNotifiManager; - - private GTaskManager mTaskManager; - - private OnCompleteListener mOnCompleteListener; - - public GTaskASyncTask(Context context, OnCompleteListener listener) { - mContext = context; - mOnCompleteListener = listener; - mNotifiManager = (NotificationManager) mContext - .getSystemService(Context.NOTIFICATION_SERVICE); - mTaskManager = GTaskManager.getInstance(); - } - - public void cancelSync() { - mTaskManager.cancelSync(); - } - - public void publishProgess(String message) { - publishProgress(new String[] { - message - }); - } - - private void showNotification(int tickerId, String content) { - Notification notification = new Notification(R.drawable.notification, mContext - .getString(tickerId), System.currentTimeMillis()); - notification.defaults = Notification.DEFAULT_LIGHTS; - notification.flags = Notification.FLAG_AUTO_CANCEL; - PendingIntent pendingIntent; - if (tickerId != R.string.ticker_success) { - pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - NotesPreferenceActivity.class), 0); - - } else { - pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, - NotesListActivity.class), 0); - } - notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, - pendingIntent); - mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); - } - - @Override - protected Integer doInBackground(Void... unused) { - publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity - .getSyncAccountName(mContext))); - return mTaskManager.sync(mContext, this); - } - - @Override - protected void onProgressUpdate(String... progress) { - showNotification(R.string.ticker_syncing, progress[0]); - if (mContext instanceof GTaskSyncService) { - ((GTaskSyncService) mContext).sendBroadcast(progress[0]); - } - } - - @Override - protected void onPostExecute(Integer result) { - if (result == GTaskManager.STATE_SUCCESS) { - showNotification(R.string.ticker_success, mContext.getString( - R.string.success_sync_account, mTaskManager.getSyncAccount())); - NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); - } else if (result == GTaskManager.STATE_NETWORK_ERROR) { - showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); - } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { - showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); - } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { - showNotification(R.string.ticker_cancel, mContext - .getString(R.string.error_sync_cancelled)); - } - if (mOnCompleteListener != null) { - new Thread(new Runnable() { - - public void run() { - mOnCompleteListener.onComplete(); - } - }).start(); - } - } -} diff --git a/src/net/micode/notes/gtask/remote/GTaskClient.java b/src/net/micode/notes/gtask/remote/GTaskClient.java deleted file mode 100644 index c67dfdf3..00000000 --- a/src/net/micode/notes/gtask/remote/GTaskClient.java +++ /dev/null @@ -1,585 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.remote; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.accounts.AccountManagerFuture; -import android.app.Activity; -import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; - -import net.micode.notes.gtask.data.Node; -import net.micode.notes.gtask.data.Task; -import net.micode.notes.gtask.data.TaskList; -import net.micode.notes.gtask.exception.ActionFailureException; -import net.micode.notes.gtask.exception.NetworkFailureException; -import net.micode.notes.tool.GTaskStringUtils; -import net.micode.notes.ui.NotesPreferenceActivity; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.cookie.Cookie; -import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpProtocolParams; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.LinkedList; -import java.util.List; -import java.util.zip.GZIPInputStream; -import java.util.zip.Inflater; -import java.util.zip.InflaterInputStream; - - -public class GTaskClient { - private static final String TAG = GTaskClient.class.getSimpleName(); - - private static final String GTASK_URL = "https://mail.google.com/tasks/"; - - private static final String GTASK_GET_URL = "https://mail.google.com/tasks/ig"; - - private static final String GTASK_POST_URL = "https://mail.google.com/tasks/r/ig"; - - private static GTaskClient mInstance = null; - - private DefaultHttpClient mHttpClient; - - private String mGetUrl; - - private String mPostUrl; - - private long mClientVersion; - - private boolean mLoggedin; - - private long mLastLoginTime; - - private int mActionId; - - private Account mAccount; - - private JSONArray mUpdateArray; - - private GTaskClient() { - mHttpClient = null; - mGetUrl = GTASK_GET_URL; - mPostUrl = GTASK_POST_URL; - mClientVersion = -1; - mLoggedin = false; - mLastLoginTime = 0; - mActionId = 1; - mAccount = null; - mUpdateArray = null; - } - - public static synchronized GTaskClient getInstance() { - if (mInstance == null) { - mInstance = new GTaskClient(); - } - return mInstance; - } - - public boolean login(Activity activity) { - // we suppose that the cookie would expire after 5 minutes - // then we need to re-login - final long interval = 1000 * 60 * 5; - if (mLastLoginTime + interval < System.currentTimeMillis()) { - mLoggedin = false; - } - - // need to re-login after account switch - if (mLoggedin - && !TextUtils.equals(getSyncAccount().name, NotesPreferenceActivity - .getSyncAccountName(activity))) { - mLoggedin = false; - } - - if (mLoggedin) { - Log.d(TAG, "already logged in"); - return true; - } - - mLastLoginTime = System.currentTimeMillis(); - String authToken = loginGoogleAccount(activity, false); - if (authToken == null) { - Log.e(TAG, "login google account failed"); - return false; - } - - // login with custom domain if necessary - if (!(mAccount.name.toLowerCase().endsWith("gmail.com") || mAccount.name.toLowerCase() - .endsWith("googlemail.com"))) { - StringBuilder url = new StringBuilder(GTASK_URL).append("a/"); - int index = mAccount.name.indexOf('@') + 1; - String suffix = mAccount.name.substring(index); - url.append(suffix + "/"); - mGetUrl = url.toString() + "ig"; - mPostUrl = url.toString() + "r/ig"; - - if (tryToLoginGtask(activity, authToken)) { - mLoggedin = true; - } - } - - // try to login with google official url - if (!mLoggedin) { - mGetUrl = GTASK_GET_URL; - mPostUrl = GTASK_POST_URL; - if (!tryToLoginGtask(activity, authToken)) { - return false; - } - } - - mLoggedin = true; - return true; - } - - private String loginGoogleAccount(Activity activity, boolean invalidateToken) { - String authToken; - AccountManager accountManager = AccountManager.get(activity); - Account[] accounts = accountManager.getAccountsByType("com.google"); - - if (accounts.length == 0) { - Log.e(TAG, "there is no available google account"); - return null; - } - - String accountName = NotesPreferenceActivity.getSyncAccountName(activity); - Account account = null; - for (Account a : accounts) { - if (a.name.equals(accountName)) { - account = a; - break; - } - } - if (account != null) { - mAccount = account; - } else { - Log.e(TAG, "unable to get an account with the same name in the settings"); - return null; - } - - // get the token now - AccountManagerFuture accountManagerFuture = accountManager.getAuthToken(account, - "goanna_mobile", null, activity, null, null); - try { - Bundle authTokenBundle = accountManagerFuture.getResult(); - authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN); - if (invalidateToken) { - accountManager.invalidateAuthToken("com.google", authToken); - loginGoogleAccount(activity, false); - } - } catch (Exception e) { - Log.e(TAG, "get auth token failed"); - authToken = null; - } - - return authToken; - } - - private boolean tryToLoginGtask(Activity activity, String authToken) { - if (!loginGtask(authToken)) { - // maybe the auth token is out of date, now let's invalidate the - // token and try again - authToken = loginGoogleAccount(activity, true); - if (authToken == null) { - Log.e(TAG, "login google account failed"); - return false; - } - - if (!loginGtask(authToken)) { - Log.e(TAG, "login gtask failed"); - return false; - } - } - return true; - } - - private boolean loginGtask(String authToken) { - int timeoutConnection = 10000; - int timeoutSocket = 15000; - HttpParams httpParameters = new BasicHttpParams(); - HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); - HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); - mHttpClient = new DefaultHttpClient(httpParameters); - BasicCookieStore localBasicCookieStore = new BasicCookieStore(); - mHttpClient.setCookieStore(localBasicCookieStore); - HttpProtocolParams.setUseExpectContinue(mHttpClient.getParams(), false); - - // login gtask - try { - String loginUrl = mGetUrl + "?auth=" + authToken; - HttpGet httpGet = new HttpGet(loginUrl); - HttpResponse response = null; - response = mHttpClient.execute(httpGet); - - // get the cookie now - List cookies = mHttpClient.getCookieStore().getCookies(); - boolean hasAuthCookie = false; - for (Cookie cookie : cookies) { - if (cookie.getName().contains("GTL")) { - hasAuthCookie = true; - } - } - if (!hasAuthCookie) { - Log.w(TAG, "it seems that there is no auth cookie"); - } - - // get the client version - String resString = getResponseContent(response.getEntity()); - String jsBegin = "_setup("; - String jsEnd = ")}"; - int begin = resString.indexOf(jsBegin); - int end = resString.lastIndexOf(jsEnd); - String jsString = null; - if (begin != -1 && end != -1 && begin < end) { - jsString = resString.substring(begin + jsBegin.length(), end); - } - JSONObject js = new JSONObject(jsString); - mClientVersion = js.getLong("v"); - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - return false; - } catch (Exception e) { - // simply catch all exceptions - Log.e(TAG, "httpget gtask_url failed"); - return false; - } - - return true; - } - - private int getActionId() { - return mActionId++; - } - - private HttpPost createHttpPost() { - HttpPost httpPost = new HttpPost(mPostUrl); - httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8"); - httpPost.setHeader("AT", "1"); - return httpPost; - } - - private String getResponseContent(HttpEntity entity) throws IOException { - String contentEncoding = null; - if (entity.getContentEncoding() != null) { - contentEncoding = entity.getContentEncoding().getValue(); - Log.d(TAG, "encoding: " + contentEncoding); - } - - InputStream input = entity.getContent(); - if (contentEncoding != null && contentEncoding.equalsIgnoreCase("gzip")) { - input = new GZIPInputStream(entity.getContent()); - } else if (contentEncoding != null && contentEncoding.equalsIgnoreCase("deflate")) { - Inflater inflater = new Inflater(true); - input = new InflaterInputStream(entity.getContent(), inflater); - } - - try { - InputStreamReader isr = new InputStreamReader(input); - BufferedReader br = new BufferedReader(isr); - StringBuilder sb = new StringBuilder(); - - while (true) { - String buff = br.readLine(); - if (buff == null) { - return sb.toString(); - } - sb = sb.append(buff); - } - } finally { - input.close(); - } - } - - private JSONObject postRequest(JSONObject js) throws NetworkFailureException { - if (!mLoggedin) { - Log.e(TAG, "please login first"); - throw new ActionFailureException("not logged in"); - } - - HttpPost httpPost = createHttpPost(); - try { - LinkedList list = new LinkedList(); - list.add(new BasicNameValuePair("r", js.toString())); - UrlEncodedFormEntity entity = new UrlEncodedFormEntity(list, "UTF-8"); - httpPost.setEntity(entity); - - // execute the post - HttpResponse response = mHttpClient.execute(httpPost); - String jsString = getResponseContent(response.getEntity()); - return new JSONObject(jsString); - - } catch (ClientProtocolException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new NetworkFailureException("postRequest failed"); - } catch (IOException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new NetworkFailureException("postRequest failed"); - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("unable to convert response content to jsonobject"); - } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("error occurs when posting request"); - } - } - - public void createTask(Task task) throws NetworkFailureException { - commitUpdate(); - try { - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray(); - - // action_list - actionList.put(task.getCreateAction(getActionId())); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - - // client_version - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - - // post - JSONObject jsResponse = postRequest(jsPost); - JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( - GTaskStringUtils.GTASK_JSON_RESULTS).get(0); - task.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("create task: handing jsonobject failed"); - } - } - - public void createTaskList(TaskList tasklist) throws NetworkFailureException { - commitUpdate(); - try { - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray(); - - // action_list - actionList.put(tasklist.getCreateAction(getActionId())); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - - // client version - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - - // post - JSONObject jsResponse = postRequest(jsPost); - JSONObject jsResult = (JSONObject) jsResponse.getJSONArray( - GTaskStringUtils.GTASK_JSON_RESULTS).get(0); - tasklist.setGid(jsResult.getString(GTaskStringUtils.GTASK_JSON_NEW_ID)); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("create tasklist: handing jsonobject failed"); - } - } - - public void commitUpdate() throws NetworkFailureException { - if (mUpdateArray != null) { - try { - JSONObject jsPost = new JSONObject(); - - // action_list - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, mUpdateArray); - - // client_version - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - - postRequest(jsPost); - mUpdateArray = null; - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("commit update: handing jsonobject failed"); - } - } - } - - public void addUpdateNode(Node node) throws NetworkFailureException { - if (node != null) { - // too many update items may result in an error - // set max to 10 items - if (mUpdateArray != null && mUpdateArray.length() > 10) { - commitUpdate(); - } - - if (mUpdateArray == null) - mUpdateArray = new JSONArray(); - mUpdateArray.put(node.getUpdateAction(getActionId())); - } - } - - public void moveTask(Task task, TaskList preParent, TaskList curParent) - throws NetworkFailureException { - commitUpdate(); - try { - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray(); - JSONObject action = new JSONObject(); - - // action_list - action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_MOVE); - action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); - action.put(GTaskStringUtils.GTASK_JSON_ID, task.getGid()); - if (preParent == curParent && task.getPriorSibling() != null) { - // put prioring_sibing_id only if moving within the tasklist and - // it is not the first one - action.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, task.getPriorSibling()); - } - action.put(GTaskStringUtils.GTASK_JSON_SOURCE_LIST, preParent.getGid()); - action.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT, curParent.getGid()); - if (preParent != curParent) { - // put the dest_list only if moving between tasklists - action.put(GTaskStringUtils.GTASK_JSON_DEST_LIST, curParent.getGid()); - } - actionList.put(action); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - - // client_version - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - - postRequest(jsPost); - - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("move task: handing jsonobject failed"); - } - } - - public void deleteNode(Node node) throws NetworkFailureException { - commitUpdate(); - try { - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray(); - - // action_list - node.setDeleted(true); - actionList.put(node.getUpdateAction(getActionId())); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - - // client_version - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - - postRequest(jsPost); - mUpdateArray = null; - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("delete node: handing jsonobject failed"); - } - } - - public JSONArray getTaskLists() throws NetworkFailureException { - if (!mLoggedin) { - Log.e(TAG, "please login first"); - throw new ActionFailureException("not logged in"); - } - - try { - HttpGet httpGet = new HttpGet(mGetUrl); - HttpResponse response = null; - response = mHttpClient.execute(httpGet); - - // get the task list - String resString = getResponseContent(response.getEntity()); - String jsBegin = "_setup("; - String jsEnd = ")}"; - int begin = resString.indexOf(jsBegin); - int end = resString.lastIndexOf(jsEnd); - String jsString = null; - if (begin != -1 && end != -1 && begin < end) { - jsString = resString.substring(begin + jsBegin.length(), end); - } - JSONObject js = new JSONObject(jsString); - return js.getJSONObject("t").getJSONArray(GTaskStringUtils.GTASK_JSON_LISTS); - } catch (ClientProtocolException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new NetworkFailureException("gettasklists: httpget failed"); - } catch (IOException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new NetworkFailureException("gettasklists: httpget failed"); - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("get task lists: handing jasonobject failed"); - } - } - - public JSONArray getTaskList(String listGid) throws NetworkFailureException { - commitUpdate(); - try { - JSONObject jsPost = new JSONObject(); - JSONArray actionList = new JSONArray(); - JSONObject action = new JSONObject(); - - // action_list - action.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, - GTaskStringUtils.GTASK_JSON_ACTION_TYPE_GETALL); - action.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, getActionId()); - action.put(GTaskStringUtils.GTASK_JSON_LIST_ID, listGid); - action.put(GTaskStringUtils.GTASK_JSON_GET_DELETED, false); - actionList.put(action); - jsPost.put(GTaskStringUtils.GTASK_JSON_ACTION_LIST, actionList); - - // client_version - jsPost.put(GTaskStringUtils.GTASK_JSON_CLIENT_VERSION, mClientVersion); - - JSONObject jsResponse = postRequest(jsPost); - return jsResponse.getJSONArray(GTaskStringUtils.GTASK_JSON_TASKS); - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("get task list: handing jsonobject failed"); - } - } - - public Account getSyncAccount() { - return mAccount; - } - - public void resetUpdateArray() { - mUpdateArray = null; - } -} diff --git a/src/net/micode/notes/gtask/remote/GTaskManager.java b/src/net/micode/notes/gtask/remote/GTaskManager.java deleted file mode 100644 index d2b4082b..00000000 --- a/src/net/micode/notes/gtask/remote/GTaskManager.java +++ /dev/null @@ -1,800 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.remote; - -import android.app.Activity; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.util.Log; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.gtask.data.MetaData; -import net.micode.notes.gtask.data.Node; -import net.micode.notes.gtask.data.SqlNote; -import net.micode.notes.gtask.data.Task; -import net.micode.notes.gtask.data.TaskList; -import net.micode.notes.gtask.exception.ActionFailureException; -import net.micode.notes.gtask.exception.NetworkFailureException; -import net.micode.notes.tool.DataUtils; -import net.micode.notes.tool.GTaskStringUtils; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; - - -public class GTaskManager { - private static final String TAG = GTaskManager.class.getSimpleName(); - - public static final int STATE_SUCCESS = 0; - - public static final int STATE_NETWORK_ERROR = 1; - - public static final int STATE_INTERNAL_ERROR = 2; - - public static final int STATE_SYNC_IN_PROGRESS = 3; - - public static final int STATE_SYNC_CANCELLED = 4; - - private static GTaskManager mInstance = null; - - private Activity mActivity; - - private Context mContext; - - private ContentResolver mContentResolver; - - private boolean mSyncing; - - private boolean mCancelled; - - private HashMap mGTaskListHashMap; - - private HashMap mGTaskHashMap; - - private HashMap mMetaHashMap; - - private TaskList mMetaList; - - private HashSet mLocalDeleteIdMap; - - private HashMap mGidToNid; - - private HashMap mNidToGid; - - private GTaskManager() { - mSyncing = false; - mCancelled = false; - mGTaskListHashMap = new HashMap(); - mGTaskHashMap = new HashMap(); - mMetaHashMap = new HashMap(); - mMetaList = null; - mLocalDeleteIdMap = new HashSet(); - mGidToNid = new HashMap(); - mNidToGid = new HashMap(); - } - - public static synchronized GTaskManager getInstance() { - if (mInstance == null) { - mInstance = new GTaskManager(); - } - return mInstance; - } - - public synchronized void setActivityContext(Activity activity) { - // used for getting authtoken - mActivity = activity; - } - - public int sync(Context context, GTaskASyncTask asyncTask) { - if (mSyncing) { - Log.d(TAG, "Sync is in progress"); - return STATE_SYNC_IN_PROGRESS; - } - mContext = context; - mContentResolver = mContext.getContentResolver(); - mSyncing = true; - mCancelled = false; - mGTaskListHashMap.clear(); - mGTaskHashMap.clear(); - mMetaHashMap.clear(); - mLocalDeleteIdMap.clear(); - mGidToNid.clear(); - mNidToGid.clear(); - - try { - GTaskClient client = GTaskClient.getInstance(); - client.resetUpdateArray(); - - // login google task - if (!mCancelled) { - if (!client.login(mActivity)) { - throw new NetworkFailureException("login google task failed"); - } - } - - // get the task list from google - asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); - initGTaskList(); - - // do content sync work - asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing)); - syncContent(); - } catch (NetworkFailureException e) { - Log.e(TAG, e.toString()); - return STATE_NETWORK_ERROR; - } catch (ActionFailureException e) { - Log.e(TAG, e.toString()); - return STATE_INTERNAL_ERROR; - } catch (Exception e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - return STATE_INTERNAL_ERROR; - } finally { - mGTaskListHashMap.clear(); - mGTaskHashMap.clear(); - mMetaHashMap.clear(); - mLocalDeleteIdMap.clear(); - mGidToNid.clear(); - mNidToGid.clear(); - mSyncing = false; - } - - return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; - } - - private void initGTaskList() throws NetworkFailureException { - if (mCancelled) - return; - GTaskClient client = GTaskClient.getInstance(); - try { - JSONArray jsTaskLists = client.getTaskLists(); - - // init meta list first - mMetaList = null; - for (int i = 0; i < jsTaskLists.length(); i++) { - JSONObject object = jsTaskLists.getJSONObject(i); - String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); - String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); - - if (name - .equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { - mMetaList = new TaskList(); - mMetaList.setContentByRemoteJSON(object); - - // load meta data - JSONArray jsMetas = client.getTaskList(gid); - for (int j = 0; j < jsMetas.length(); j++) { - object = (JSONObject) jsMetas.getJSONObject(j); - MetaData metaData = new MetaData(); - metaData.setContentByRemoteJSON(object); - if (metaData.isWorthSaving()) { - mMetaList.addChildTask(metaData); - if (metaData.getGid() != null) { - mMetaHashMap.put(metaData.getRelatedGid(), metaData); - } - } - } - } - } - - // create meta list if not existed - if (mMetaList == null) { - mMetaList = new TaskList(); - mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_META); - GTaskClient.getInstance().createTaskList(mMetaList); - } - - // init task list - for (int i = 0; i < jsTaskLists.length(); i++) { - JSONObject object = jsTaskLists.getJSONObject(i); - String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); - String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); - - if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) - && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_META)) { - TaskList tasklist = new TaskList(); - tasklist.setContentByRemoteJSON(object); - mGTaskListHashMap.put(gid, tasklist); - mGTaskHashMap.put(gid, tasklist); - - // load tasks - JSONArray jsTasks = client.getTaskList(gid); - for (int j = 0; j < jsTasks.length(); j++) { - object = (JSONObject) jsTasks.getJSONObject(j); - gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); - Task task = new Task(); - task.setContentByRemoteJSON(object); - if (task.isWorthSaving()) { - task.setMetaInfo(mMetaHashMap.get(gid)); - tasklist.addChildTask(task); - mGTaskHashMap.put(gid, task); - } - } - } - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("initGTaskList: handing JSONObject failed"); - } - } - - private void syncContent() throws NetworkFailureException { - int syncType; - Cursor c = null; - String gid; - Node node; - - mLocalDeleteIdMap.clear(); - - if (mCancelled) { - return; - } - - // for local deleted note - try { - c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type<>? AND parent_id=?)", new String[] { - String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) - }, null); - if (c != null) { - while (c.moveToNext()) { - gid = c.getString(SqlNote.GTASK_ID_COLUMN); - node = mGTaskHashMap.get(gid); - if (node != null) { - mGTaskHashMap.remove(gid); - doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); - } - - mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); - } - } else { - Log.w(TAG, "failed to query trash folder"); - } - } finally { - if (c != null) { - c.close(); - c = null; - } - } - - // sync folder first - syncFolder(); - - // for note existing in database - try { - c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type=? AND parent_id<>?)", new String[] { - String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER) - }, NoteColumns.TYPE + " DESC"); - if (c != null) { - while (c.moveToNext()) { - gid = c.getString(SqlNote.GTASK_ID_COLUMN); - node = mGTaskHashMap.get(gid); - if (node != null) { - mGTaskHashMap.remove(gid); - mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); - mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); - syncType = node.getSyncAction(c); - } else { - if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add - syncType = Node.SYNC_ACTION_ADD_REMOTE; - } else { - // remote delete - syncType = Node.SYNC_ACTION_DEL_LOCAL; - } - } - doContentSync(syncType, node, c); - } - } else { - Log.w(TAG, "failed to query existing note in database"); - } - - } finally { - if (c != null) { - c.close(); - c = null; - } - } - - // go through remaining items - Iterator> iter = mGTaskHashMap.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry entry = iter.next(); - node = entry.getValue(); - doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); - } - - // mCancelled can be set by another thread, so we neet to check one by - // one - // clear local delete table - if (!mCancelled) { - if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { - throw new ActionFailureException("failed to batch-delete local deleted notes"); - } - } - - // refresh local sync id - if (!mCancelled) { - GTaskClient.getInstance().commitUpdate(); - refreshLocalSyncId(); - } - - } - - private void syncFolder() throws NetworkFailureException { - Cursor c = null; - String gid; - Node node; - int syncType; - - if (mCancelled) { - return; - } - - // for root folder - try { - c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, - Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null); - if (c != null) { - c.moveToNext(); - gid = c.getString(SqlNote.GTASK_ID_COLUMN); - node = mGTaskHashMap.get(gid); - if (node != null) { - mGTaskHashMap.remove(gid); - mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER); - mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid); - // for system folder, only update remote name if necessary - if (!node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) - doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); - } else { - doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); - } - } else { - Log.w(TAG, "failed to query root folder"); - } - } finally { - if (c != null) { - c.close(); - c = null; - } - } - - // for call-note folder - try { - c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)", - new String[] { - String.valueOf(Notes.ID_CALL_RECORD_FOLDER) - }, null); - if (c != null) { - if (c.moveToNext()) { - gid = c.getString(SqlNote.GTASK_ID_COLUMN); - node = mGTaskHashMap.get(gid); - if (node != null) { - mGTaskHashMap.remove(gid); - mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER); - mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); - // for system folder, only update remote name if - // necessary - if (!node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX - + GTaskStringUtils.FOLDER_CALL_NOTE)) - doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c); - } else { - doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); - } - } - } else { - Log.w(TAG, "failed to query call note folder"); - } - } finally { - if (c != null) { - c.close(); - c = null; - } - } - - // for local existing folders - try { - c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type=? AND parent_id<>?)", new String[] { - String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) - }, NoteColumns.TYPE + " DESC"); - if (c != null) { - while (c.moveToNext()) { - gid = c.getString(SqlNote.GTASK_ID_COLUMN); - node = mGTaskHashMap.get(gid); - if (node != null) { - mGTaskHashMap.remove(gid); - mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); - mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); - syncType = node.getSyncAction(c); - } else { - if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add - syncType = Node.SYNC_ACTION_ADD_REMOTE; - } else { - // remote delete - syncType = Node.SYNC_ACTION_DEL_LOCAL; - } - } - doContentSync(syncType, node, c); - } - } else { - Log.w(TAG, "failed to query existing folder"); - } - } finally { - if (c != null) { - c.close(); - c = null; - } - } - - // for remote add folders - Iterator> iter = mGTaskListHashMap.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry entry = iter.next(); - gid = entry.getKey(); - node = entry.getValue(); - if (mGTaskHashMap.containsKey(gid)) { - mGTaskHashMap.remove(gid); - doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); - } - } - - if (!mCancelled) - GTaskClient.getInstance().commitUpdate(); - } - - private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { - if (mCancelled) { - return; - } - - MetaData meta; - switch (syncType) { - case Node.SYNC_ACTION_ADD_LOCAL: - addLocalNode(node); - break; - case Node.SYNC_ACTION_ADD_REMOTE: - addRemoteNode(node, c); - break; - case Node.SYNC_ACTION_DEL_LOCAL: - meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN)); - if (meta != null) { - GTaskClient.getInstance().deleteNode(meta); - } - mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); - break; - case Node.SYNC_ACTION_DEL_REMOTE: - meta = mMetaHashMap.get(node.getGid()); - if (meta != null) { - GTaskClient.getInstance().deleteNode(meta); - } - GTaskClient.getInstance().deleteNode(node); - break; - case Node.SYNC_ACTION_UPDATE_LOCAL: - updateLocalNode(node, c); - break; - case Node.SYNC_ACTION_UPDATE_REMOTE: - updateRemoteNode(node, c); - break; - case Node.SYNC_ACTION_UPDATE_CONFLICT: - // merging both modifications maybe a good idea - // right now just use local update simply - updateRemoteNode(node, c); - break; - case Node.SYNC_ACTION_NONE: - break; - case Node.SYNC_ACTION_ERROR: - default: - throw new ActionFailureException("unkown sync action type"); - } - } - - private void addLocalNode(Node node) throws NetworkFailureException { - if (mCancelled) { - return; - } - - SqlNote sqlNote; - if (node instanceof TaskList) { - if (node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) { - sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER); - } else if (node.getName().equals( - GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { - sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); - } else { - sqlNote = new SqlNote(mContext); - sqlNote.setContent(node.getLocalJSONFromContent()); - sqlNote.setParentId(Notes.ID_ROOT_FOLDER); - } - } else { - sqlNote = new SqlNote(mContext); - JSONObject js = node.getLocalJSONFromContent(); - try { - if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { - JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); - if (note.has(NoteColumns.ID)) { - long id = note.getLong(NoteColumns.ID); - if (DataUtils.existInNoteDatabase(mContentResolver, id)) { - // the id is not available, have to create a new one - note.remove(NoteColumns.ID); - } - } - } - - if (js.has(GTaskStringUtils.META_HEAD_DATA)) { - JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); - for (int i = 0; i < dataArray.length(); i++) { - JSONObject data = dataArray.getJSONObject(i); - if (data.has(DataColumns.ID)) { - long dataId = data.getLong(DataColumns.ID); - if (DataUtils.existInDataDatabase(mContentResolver, dataId)) { - // the data id is not available, have to create - // a new one - data.remove(DataColumns.ID); - } - } - } - - } - } catch (JSONException e) { - Log.w(TAG, e.toString()); - e.printStackTrace(); - } - sqlNote.setContent(js); - - Long parentId = mGidToNid.get(((Task) node).getParent().getGid()); - if (parentId == null) { - Log.e(TAG, "cannot find task's parent id locally"); - throw new ActionFailureException("cannot add local node"); - } - sqlNote.setParentId(parentId.longValue()); - } - - // create the local node - sqlNote.setGtaskId(node.getGid()); - sqlNote.commit(false); - - // update gid-nid mapping - mGidToNid.put(node.getGid(), sqlNote.getId()); - mNidToGid.put(sqlNote.getId(), node.getGid()); - - // update meta - updateRemoteMeta(node.getGid(), sqlNote); - } - - private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException { - if (mCancelled) { - return; - } - - SqlNote sqlNote; - // update the note locally - sqlNote = new SqlNote(mContext, c); - sqlNote.setContent(node.getLocalJSONFromContent()); - - Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) - : new Long(Notes.ID_ROOT_FOLDER); - if (parentId == null) { - Log.e(TAG, "cannot find task's parent id locally"); - throw new ActionFailureException("cannot update local node"); - } - sqlNote.setParentId(parentId.longValue()); - sqlNote.commit(true); - - // update meta info - updateRemoteMeta(node.getGid(), sqlNote); - } - - private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { - if (mCancelled) { - return; - } - - SqlNote sqlNote = new SqlNote(mContext, c); - Node n; - - // update remotely - if (sqlNote.isNoteType()) { - Task task = new Task(); - task.setContentByLocalJSON(sqlNote.getContent()); - - String parentGid = mNidToGid.get(sqlNote.getParentId()); - if (parentGid == null) { - Log.e(TAG, "cannot find task's parent tasklist"); - throw new ActionFailureException("cannot add remote task"); - } - mGTaskListHashMap.get(parentGid).addChildTask(task); - - GTaskClient.getInstance().createTask(task); - n = (Node) task; - - // add meta - updateRemoteMeta(task.getGid(), sqlNote); - } else { - TaskList tasklist = null; - - // we need to skip folder if it has already existed - String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX; - if (sqlNote.getId() == Notes.ID_ROOT_FOLDER) - folderName += GTaskStringUtils.FOLDER_DEFAULT; - else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER) - folderName += GTaskStringUtils.FOLDER_CALL_NOTE; - else - folderName += sqlNote.getSnippet(); - - Iterator> iter = mGTaskListHashMap.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry entry = iter.next(); - String gid = entry.getKey(); - TaskList list = entry.getValue(); - - if (list.getName().equals(folderName)) { - tasklist = list; - if (mGTaskHashMap.containsKey(gid)) { - mGTaskHashMap.remove(gid); - } - break; - } - } - - // no match we can add now - if (tasklist == null) { - tasklist = new TaskList(); - tasklist.setContentByLocalJSON(sqlNote.getContent()); - GTaskClient.getInstance().createTaskList(tasklist); - mGTaskListHashMap.put(tasklist.getGid(), tasklist); - } - n = (Node) tasklist; - } - - // update local note - sqlNote.setGtaskId(n.getGid()); - sqlNote.commit(false); - sqlNote.resetLocalModified(); - sqlNote.commit(true); - - // gid-id mapping - mGidToNid.put(n.getGid(), sqlNote.getId()); - mNidToGid.put(sqlNote.getId(), n.getGid()); - } - - private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { - if (mCancelled) { - return; - } - - SqlNote sqlNote = new SqlNote(mContext, c); - - // update remotely - node.setContentByLocalJSON(sqlNote.getContent()); - GTaskClient.getInstance().addUpdateNode(node); - - // update meta - updateRemoteMeta(node.getGid(), sqlNote); - - // move task if necessary - if (sqlNote.isNoteType()) { - Task task = (Task) node; - TaskList preParentList = task.getParent(); - - String curParentGid = mNidToGid.get(sqlNote.getParentId()); - if (curParentGid == null) { - Log.e(TAG, "cannot find task's parent tasklist"); - throw new ActionFailureException("cannot update remote task"); - } - TaskList curParentList = mGTaskListHashMap.get(curParentGid); - - if (preParentList != curParentList) { - preParentList.removeChildTask(task); - curParentList.addChildTask(task); - GTaskClient.getInstance().moveTask(task, preParentList, curParentList); - } - } - - // clear local modified flag - sqlNote.resetLocalModified(); - sqlNote.commit(true); - } - - private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { - if (sqlNote != null && sqlNote.isNoteType()) { - MetaData metaData = mMetaHashMap.get(gid); - if (metaData != null) { - metaData.setMeta(gid, sqlNote.getContent()); - GTaskClient.getInstance().addUpdateNode(metaData); - } else { - metaData = new MetaData(); - metaData.setMeta(gid, sqlNote.getContent()); - mMetaList.addChildTask(metaData); - mMetaHashMap.put(gid, metaData); - GTaskClient.getInstance().createTask(metaData); - } - } - } - - private void refreshLocalSyncId() throws NetworkFailureException { - if (mCancelled) { - return; - } - - // get the latest gtask list - mGTaskHashMap.clear(); - mGTaskListHashMap.clear(); - mMetaHashMap.clear(); - initGTaskList(); - - Cursor c = null; - try { - c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, - "(type<>? AND parent_id<>?)", new String[] { - String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) - }, NoteColumns.TYPE + " DESC"); - if (c != null) { - while (c.moveToNext()) { - String gid = c.getString(SqlNote.GTASK_ID_COLUMN); - Node node = mGTaskHashMap.get(gid); - if (node != null) { - mGTaskHashMap.remove(gid); - ContentValues values = new ContentValues(); - values.put(NoteColumns.SYNC_ID, node.getLastModified()); - mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, - c.getLong(SqlNote.ID_COLUMN)), values, null, null); - } else { - Log.e(TAG, "something is missed"); - throw new ActionFailureException( - "some local items don't have gid after sync"); - } - } - } else { - Log.w(TAG, "failed to query local note to refresh sync id"); - } - } finally { - if (c != null) { - c.close(); - c = null; - } - } - } - - public String getSyncAccount() { - return GTaskClient.getInstance().getSyncAccount().name; - } - - public void cancelSync() { - mCancelled = true; - } -} diff --git a/src/net/micode/notes/gtask/remote/GTaskSyncService.java b/src/net/micode/notes/gtask/remote/GTaskSyncService.java deleted file mode 100644 index cca36f76..00000000 --- a/src/net/micode/notes/gtask/remote/GTaskSyncService.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.gtask.remote; - -import android.app.Activity; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.IBinder; - -public class GTaskSyncService extends Service { - public final static String ACTION_STRING_NAME = "sync_action_type"; - - public final static int ACTION_START_SYNC = 0; - - public final static int ACTION_CANCEL_SYNC = 1; - - public final static int ACTION_INVALID = 2; - - public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; - - public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; - - public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; - - private static GTaskASyncTask mSyncTask = null; - - private static String mSyncProgress = ""; - - private void startSync() { - if (mSyncTask == null) { - mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { - public void onComplete() { - mSyncTask = null; - sendBroadcast(""); - stopSelf(); - } - }); - sendBroadcast(""); - mSyncTask.execute(); - } - } - - private void cancelSync() { - if (mSyncTask != null) { - mSyncTask.cancelSync(); - } - } - - @Override - public void onCreate() { - mSyncTask = null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Bundle bundle = intent.getExtras(); - if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { - switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { - case ACTION_START_SYNC: - startSync(); - break; - case ACTION_CANCEL_SYNC: - cancelSync(); - break; - default: - break; - } - return START_STICKY; - } - return super.onStartCommand(intent, flags, startId); - } - - @Override - public void onLowMemory() { - if (mSyncTask != null) { - mSyncTask.cancelSync(); - } - } - - public IBinder onBind(Intent intent) { - return null; - } - - public void sendBroadcast(String msg) { - mSyncProgress = msg; - Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); - intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); - intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); - sendBroadcast(intent); - } - - public static void startSync(Activity activity) { - GTaskManager.getInstance().setActivityContext(activity); - Intent intent = new Intent(activity, GTaskSyncService.class); - intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); - activity.startService(intent); - } - - public static void cancelSync(Context context) { - Intent intent = new Intent(context, GTaskSyncService.class); - intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); - context.startService(intent); - } - - public static boolean isSyncing() { - return mSyncTask != null; - } - - public static String getProgressString() { - return mSyncProgress; - } -} diff --git a/src/net/micode/notes/model/Note.java b/src/net/micode/notes/model/Note.java deleted file mode 100644 index 6706cf67..00000000 --- a/src/net/micode/notes/model/Note.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.model; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; -import android.content.OperationApplicationException; -import android.net.Uri; -import android.os.RemoteException; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.CallNote; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.Notes.TextNote; - -import java.util.ArrayList; - - -public class Note { - private ContentValues mNoteDiffValues; - private NoteData mNoteData; - private static final String TAG = "Note"; - /** - * Create a new note id for adding a new note to databases - */ - public static synchronized long getNewNoteId(Context context, long folderId) { - // Create a new note in the database - ContentValues values = new ContentValues(); - long createdTime = System.currentTimeMillis(); - values.put(NoteColumns.CREATED_DATE, createdTime); - values.put(NoteColumns.MODIFIED_DATE, createdTime); - values.put(NoteColumns.TYPE, Notes.TYPE_NOTE); - values.put(NoteColumns.LOCAL_MODIFIED, 1); - values.put(NoteColumns.PARENT_ID, folderId); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values); - - long noteId = 0; - try { - noteId = Long.valueOf(uri.getPathSegments().get(1)); - } catch (NumberFormatException e) { - Log.e(TAG, "Get note id error :" + e.toString()); - noteId = 0; - } - if (noteId == -1) { - throw new IllegalStateException("Wrong note id:" + noteId); - } - return noteId; - } - - public Note() { - mNoteDiffValues = new ContentValues(); - mNoteData = new NoteData(); - } - - public void setNoteValue(String key, String value) { - mNoteDiffValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); - } - - public void setTextData(String key, String value) { - mNoteData.setTextData(key, value); - } - - public void setTextDataId(long id) { - mNoteData.setTextDataId(id); - } - - public long getTextDataId() { - return mNoteData.mTextDataId; - } - - public void setCallDataId(long id) { - mNoteData.setCallDataId(id); - } - - public void setCallData(String key, String value) { - mNoteData.setCallData(key, value); - } - - public boolean isLocalModified() { - return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified(); - } - - public boolean syncNote(Context context, long noteId) { - if (noteId <= 0) { - throw new IllegalArgumentException("Wrong note id:" + noteId); - } - - if (!isLocalModified()) { - return true; - } - - /** - * In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and - * {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the - * note data info - */ - if (context.getContentResolver().update( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null, - null) == 0) { - Log.e(TAG, "Update note error, should not happen"); - // Do not return, fall through - } - mNoteDiffValues.clear(); - - if (mNoteData.isLocalModified() - && (mNoteData.pushIntoContentResolver(context, noteId) == null)) { - return false; - } - - return true; - } - - private class NoteData { - private long mTextDataId; - - private ContentValues mTextDataValues; - - private long mCallDataId; - - private ContentValues mCallDataValues; - - private static final String TAG = "NoteData"; - - public NoteData() { - mTextDataValues = new ContentValues(); - mCallDataValues = new ContentValues(); - mTextDataId = 0; - mCallDataId = 0; - } - - boolean isLocalModified() { - return mTextDataValues.size() > 0 || mCallDataValues.size() > 0; - } - - void setTextDataId(long id) { - if(id <= 0) { - throw new IllegalArgumentException("Text data id should larger than 0"); - } - mTextDataId = id; - } - - void setCallDataId(long id) { - if (id <= 0) { - throw new IllegalArgumentException("Call data id should larger than 0"); - } - mCallDataId = id; - } - - void setCallData(String key, String value) { - mCallDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); - } - - void setTextData(String key, String value) { - mTextDataValues.put(key, value); - mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1); - mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); - } - - Uri pushIntoContentResolver(Context context, long noteId) { - /** - * Check for safety - */ - if (noteId <= 0) { - throw new IllegalArgumentException("Wrong note id:" + noteId); - } - - ArrayList operationList = new ArrayList(); - ContentProviderOperation.Builder builder = null; - - if(mTextDataValues.size() > 0) { - mTextDataValues.put(DataColumns.NOTE_ID, noteId); - if (mTextDataId == 0) { - mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, - mTextDataValues); - try { - setTextDataId(Long.valueOf(uri.getPathSegments().get(1))); - } catch (NumberFormatException e) { - Log.e(TAG, "Insert new text data fail with noteId" + noteId); - mTextDataValues.clear(); - return null; - } - } else { - builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mTextDataId)); - builder.withValues(mTextDataValues); - operationList.add(builder.build()); - } - mTextDataValues.clear(); - } - - if(mCallDataValues.size() > 0) { - mCallDataValues.put(DataColumns.NOTE_ID, noteId); - if (mCallDataId == 0) { - mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE); - Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI, - mCallDataValues); - try { - setCallDataId(Long.valueOf(uri.getPathSegments().get(1))); - } catch (NumberFormatException e) { - Log.e(TAG, "Insert new call data fail with noteId" + noteId); - mCallDataValues.clear(); - return null; - } - } else { - builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId( - Notes.CONTENT_DATA_URI, mCallDataId)); - builder.withValues(mCallDataValues); - operationList.add(builder.build()); - } - mCallDataValues.clear(); - } - - if (operationList.size() > 0) { - try { - ContentProviderResult[] results = context.getContentResolver().applyBatch( - Notes.AUTHORITY, operationList); - return (results == null || results.length == 0 || results[0] == null) ? null - : ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId); - } catch (RemoteException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; - } catch (OperationApplicationException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - return null; - } - } - return null; - } - } -} diff --git a/src/net/micode/notes/model/WorkingNote.java b/src/net/micode/notes/model/WorkingNote.java deleted file mode 100644 index be081e43..00000000 --- a/src/net/micode/notes/model/WorkingNote.java +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.model; - -import android.appwidget.AppWidgetManager; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.text.TextUtils; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.CallNote; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.DataConstants; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.data.Notes.TextNote; -import net.micode.notes.tool.ResourceParser.NoteBgResources; - - -public class WorkingNote { - // Note for the working note - private Note mNote; - // Note Id - private long mNoteId; - // Note content - private String mContent; - // Note mode - private int mMode; - - private long mAlertDate; - - private long mModifiedDate; - - private int mBgColorId; - - private int mWidgetId; - - private int mWidgetType; - - private long mFolderId; - - private Context mContext; - - private static final String TAG = "WorkingNote"; - - private boolean mIsDeleted; - - private NoteSettingChangedListener mNoteSettingStatusListener; - - public static final String[] DATA_PROJECTION = new String[] { - DataColumns.ID, - DataColumns.CONTENT, - DataColumns.MIME_TYPE, - DataColumns.DATA1, - DataColumns.DATA2, - DataColumns.DATA3, - DataColumns.DATA4, - }; - - public static final String[] NOTE_PROJECTION = new String[] { - NoteColumns.PARENT_ID, - NoteColumns.ALERTED_DATE, - NoteColumns.BG_COLOR_ID, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, - NoteColumns.MODIFIED_DATE - }; - - private static final int DATA_ID_COLUMN = 0; - - private static final int DATA_CONTENT_COLUMN = 1; - - private static final int DATA_MIME_TYPE_COLUMN = 2; - - private static final int DATA_MODE_COLUMN = 3; - - private static final int NOTE_PARENT_ID_COLUMN = 0; - - private static final int NOTE_ALERTED_DATE_COLUMN = 1; - - private static final int NOTE_BG_COLOR_ID_COLUMN = 2; - - private static final int NOTE_WIDGET_ID_COLUMN = 3; - - private static final int NOTE_WIDGET_TYPE_COLUMN = 4; - - private static final int NOTE_MODIFIED_DATE_COLUMN = 5; - - // New note construct - private WorkingNote(Context context, long folderId) { - mContext = context; - mAlertDate = 0; - mModifiedDate = System.currentTimeMillis(); - mFolderId = folderId; - mNote = new Note(); - mNoteId = 0; - mIsDeleted = false; - mMode = 0; - mWidgetType = Notes.TYPE_WIDGET_INVALIDE; - } - - // Existing note construct - private WorkingNote(Context context, long noteId, long folderId) { - mContext = context; - mNoteId = noteId; - mFolderId = folderId; - mIsDeleted = false; - mNote = new Note(); - loadNote(); - } - - private void loadNote() { - Cursor cursor = mContext.getContentResolver().query( - ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, - null, null); - - if (cursor != null) { - if (cursor.moveToFirst()) { - mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); - mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); - mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); - mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN); - mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN); - mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN); - } - cursor.close(); - } else { - Log.e(TAG, "No note with id:" + mNoteId); - throw new IllegalArgumentException("Unable to find note with id " + mNoteId); - } - loadNoteData(); - } - - private void loadNoteData() { - Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, - DataColumns.NOTE_ID + "=?", new String[] { - String.valueOf(mNoteId) - }, null); - - if (cursor != null) { - if (cursor.moveToFirst()) { - do { - String type = cursor.getString(DATA_MIME_TYPE_COLUMN); - if (DataConstants.NOTE.equals(type)) { - mContent = cursor.getString(DATA_CONTENT_COLUMN); - mMode = cursor.getInt(DATA_MODE_COLUMN); - mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); - } else if (DataConstants.CALL_NOTE.equals(type)) { - mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); - } else { - Log.d(TAG, "Wrong note type with type:" + type); - } - } while (cursor.moveToNext()); - } - cursor.close(); - } else { - Log.e(TAG, "No data with id:" + mNoteId); - throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId); - } - } - - public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, - int widgetType, int defaultBgColorId) { - WorkingNote note = new WorkingNote(context, folderId); - note.setBgColorId(defaultBgColorId); - note.setWidgetId(widgetId); - note.setWidgetType(widgetType); - return note; - } - - public static WorkingNote load(Context context, long id) { - return new WorkingNote(context, id, 0); - } - - public synchronized boolean saveNote() { - if (isWorthSaving()) { - if (!existInDatabase()) { - if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { - Log.e(TAG, "Create new note fail with id:" + mNoteId); - return false; - } - } - - mNote.syncNote(mContext, mNoteId); - - /** - * Update widget content if there exist any widget of this note - */ - if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && mWidgetType != Notes.TYPE_WIDGET_INVALIDE - && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); - } - return true; - } else { - return false; - } - } - - public boolean existInDatabase() { - return mNoteId > 0; - } - - private boolean isWorthSaving() { - if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) - || (existInDatabase() && !mNote.isLocalModified())) { - return false; - } else { - return true; - } - } - - public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { - mNoteSettingStatusListener = l; - } - - public void setAlertDate(long date, boolean set) { - if (date != mAlertDate) { - mAlertDate = date; - mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); - } - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onClockAlertChanged(date, set); - } - } - - public void markDeleted(boolean mark) { - mIsDeleted = mark; - if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); - } - } - - public void setBgColorId(int id) { - if (id != mBgColorId) { - mBgColorId = id; - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onBackgroundColorChanged(); - } - mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); - } - } - - public void setCheckListMode(int mode) { - if (mMode != mode) { - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); - } - mMode = mode; - mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); - } - } - - public void setWidgetType(int type) { - if (type != mWidgetType) { - mWidgetType = type; - mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); - } - } - - public void setWidgetId(int id) { - if (id != mWidgetId) { - mWidgetId = id; - mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); - } - } - - public void setWorkingText(String text) { - if (!TextUtils.equals(mContent, text)) { - mContent = text; - mNote.setTextData(DataColumns.CONTENT, mContent); - } - } - - public void convertToCallNote(String phoneNumber, long callDate) { - mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); - mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); - mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); - } - - public boolean hasClockAlert() { - return (mAlertDate > 0 ? true : false); - } - - public String getContent() { - return mContent; - } - - public long getAlertDate() { - return mAlertDate; - } - - public long getModifiedDate() { - return mModifiedDate; - } - - public int getBgColorResId() { - return NoteBgResources.getNoteBgResource(mBgColorId); - } - - public int getBgColorId() { - return mBgColorId; - } - - public int getTitleBgResId() { - return NoteBgResources.getNoteTitleBgResource(mBgColorId); - } - - public int getCheckListMode() { - return mMode; - } - - public long getNoteId() { - return mNoteId; - } - - public long getFolderId() { - return mFolderId; - } - - public int getWidgetId() { - return mWidgetId; - } - - public int getWidgetType() { - return mWidgetType; - } - - public interface NoteSettingChangedListener { - /** - * Called when the background color of current note has just changed - */ - void onBackgroundColorChanged(); - - /** - * Called when user set clock - */ - void onClockAlertChanged(long date, boolean set); - - /** - * Call when user create note from widget - */ - void onWidgetChanged(); - - /** - * Call when switch between check list mode and normal mode - * @param oldMode is previous mode before change - * @param newMode is new mode - */ - void onCheckListModeChanged(int oldMode, int newMode); - } -} diff --git a/src/net/micode/notes/tool/BackupUtils.java b/src/net/micode/notes/tool/BackupUtils.java deleted file mode 100644 index 39f6ec47..00000000 --- a/src/net/micode/notes/tool/BackupUtils.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.tool; - -import android.content.Context; -import android.database.Cursor; -import android.os.Environment; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.util.Log; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.DataColumns; -import net.micode.notes.data.Notes.DataConstants; -import net.micode.notes.data.Notes.NoteColumns; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintStream; - - -public class BackupUtils { - private static final String TAG = "BackupUtils"; - // Singleton stuff - private static BackupUtils sInstance; - - public static synchronized BackupUtils getInstance(Context context) { - if (sInstance == null) { - sInstance = new BackupUtils(context); - } - return sInstance; - } - - /** - * Following states are signs to represents backup or restore - * status - */ - // Currently, the sdcard is not mounted - public static final int STATE_SD_CARD_UNMOUONTED = 0; - // The backup file not exist - public static final int STATE_BACKUP_FILE_NOT_EXIST = 1; - // The data is not well formated, may be changed by other programs - public static final int STATE_DATA_DESTROIED = 2; - // Some run-time exception which causes restore or backup fails - public static final int STATE_SYSTEM_ERROR = 3; - // Backup or restore success - public static final int STATE_SUCCESS = 4; - - private TextExport mTextExport; - - private BackupUtils(Context context) { - mTextExport = new TextExport(context); - } - - private static boolean externalStorageAvailable() { - return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); - } - - public int exportToText() { - return mTextExport.exportToText(); - } - - public String getExportedTextFileName() { - return mTextExport.mFileName; - } - - public String getExportedTextFileDir() { - return mTextExport.mFileDirectory; - } - - private static class TextExport { - private static final String[] NOTE_PROJECTION = { - NoteColumns.ID, - NoteColumns.MODIFIED_DATE, - NoteColumns.SNIPPET, - NoteColumns.TYPE - }; - - private static final int NOTE_COLUMN_ID = 0; - - private static final int NOTE_COLUMN_MODIFIED_DATE = 1; - - private static final int NOTE_COLUMN_SNIPPET = 2; - - private static final String[] DATA_PROJECTION = { - DataColumns.CONTENT, - DataColumns.MIME_TYPE, - DataColumns.DATA1, - DataColumns.DATA2, - DataColumns.DATA3, - DataColumns.DATA4, - }; - - private static final int DATA_COLUMN_CONTENT = 0; - - private static final int DATA_COLUMN_MIME_TYPE = 1; - - private static final int DATA_COLUMN_CALL_DATE = 2; - - private static final int DATA_COLUMN_PHONE_NUMBER = 4; - - private final String [] TEXT_FORMAT; - private static final int FORMAT_FOLDER_NAME = 0; - private static final int FORMAT_NOTE_DATE = 1; - private static final int FORMAT_NOTE_CONTENT = 2; - - private Context mContext; - private String mFileName; - private String mFileDirectory; - - public TextExport(Context context) { - TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note); - mContext = context; - mFileName = ""; - mFileDirectory = ""; - } - - private String getFormat(int id) { - return TEXT_FORMAT[id]; - } - - /** - * Export the folder identified by folder id to text - */ - private void exportFolderToText(String folderId, PrintStream ps) { - // Query notes belong to this folder - Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI, - NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] { - folderId - }, null); - - if (notesCursor != null) { - if (notesCursor.moveToFirst()) { - do { - // Print note's last modified date - ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( - mContext.getString(R.string.format_datetime_mdhm), - notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note - String noteId = notesCursor.getString(NOTE_COLUMN_ID); - exportNoteToText(noteId, ps); - } while (notesCursor.moveToNext()); - } - notesCursor.close(); - } - } - - /** - * Export note identified by id to a print stream - */ - private void exportNoteToText(String noteId, PrintStream ps) { - Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, - DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { - noteId - }, null); - - if (dataCursor != null) { - if (dataCursor.moveToFirst()) { - do { - String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE); - if (DataConstants.CALL_NOTE.equals(mimeType)) { - // Print phone number - String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER); - long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE); - String location = dataCursor.getString(DATA_COLUMN_CONTENT); - - if (!TextUtils.isEmpty(phoneNumber)) { - ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), - phoneNumber)); - } - // Print call date - ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat - .format(mContext.getString(R.string.format_datetime_mdhm), - callDate))); - // Print call attachment location - if (!TextUtils.isEmpty(location)) { - ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), - location)); - } - } else if (DataConstants.NOTE.equals(mimeType)) { - String content = dataCursor.getString(DATA_COLUMN_CONTENT); - if (!TextUtils.isEmpty(content)) { - ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), - content)); - } - } - } while (dataCursor.moveToNext()); - } - dataCursor.close(); - } - // print a line separator between note - try { - ps.write(new byte[] { - Character.LINE_SEPARATOR, Character.LETTER_NUMBER - }); - } catch (IOException e) { - Log.e(TAG, e.toString()); - } - } - - /** - * Note will be exported as text which is user readable - */ - public int exportToText() { - if (!externalStorageAvailable()) { - Log.d(TAG, "Media was not mounted"); - return STATE_SD_CARD_UNMOUONTED; - } - - PrintStream ps = getExportToTextPrintStream(); - if (ps == null) { - Log.e(TAG, "get print stream error"); - return STATE_SYSTEM_ERROR; - } - // First export folder and its notes - Cursor folderCursor = mContext.getContentResolver().query( - Notes.CONTENT_NOTE_URI, - NOTE_PROJECTION, - "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND " - + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR " - + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null); - - if (folderCursor != null) { - if (folderCursor.moveToFirst()) { - do { - // Print folder's name - String folderName = ""; - if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) { - folderName = mContext.getString(R.string.call_record_folder_name); - } else { - folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET); - } - if (!TextUtils.isEmpty(folderName)) { - ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName)); - } - String folderId = folderCursor.getString(NOTE_COLUMN_ID); - exportFolderToText(folderId, ps); - } while (folderCursor.moveToNext()); - } - folderCursor.close(); - } - - // Export notes in root's folder - Cursor noteCursor = mContext.getContentResolver().query( - Notes.CONTENT_NOTE_URI, - NOTE_PROJECTION, - NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID - + "=0", null, null); - - if (noteCursor != null) { - if (noteCursor.moveToFirst()) { - do { - ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format( - mContext.getString(R.string.format_datetime_mdhm), - noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE)))); - // Query data belong to this note - String noteId = noteCursor.getString(NOTE_COLUMN_ID); - exportNoteToText(noteId, ps); - } while (noteCursor.moveToNext()); - } - noteCursor.close(); - } - ps.close(); - - return STATE_SUCCESS; - } - - /** - * Get a print stream pointed to the file {@generateExportedTextFile} - */ - private PrintStream getExportToTextPrintStream() { - File file = generateFileMountedOnSDcard(mContext, R.string.file_path, - R.string.file_name_txt_format); - if (file == null) { - Log.e(TAG, "create file to exported failed"); - return null; - } - mFileName = file.getName(); - mFileDirectory = mContext.getString(R.string.file_path); - PrintStream ps = null; - try { - FileOutputStream fos = new FileOutputStream(file); - ps = new PrintStream(fos); - } catch (FileNotFoundException e) { - e.printStackTrace(); - return null; - } catch (NullPointerException e) { - e.printStackTrace(); - return null; - } - return ps; - } - } - - /** - * Generate the text file to store imported data - */ - private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) { - StringBuilder sb = new StringBuilder(); - sb.append(Environment.getExternalStorageDirectory()); - sb.append(context.getString(filePathResId)); - File filedir = new File(sb.toString()); - sb.append(context.getString( - fileNameFormatResId, - DateFormat.format(context.getString(R.string.format_date_ymd), - System.currentTimeMillis()))); - File file = new File(sb.toString()); - - try { - if (!filedir.exists()) { - filedir.mkdir(); - } - if (!file.exists()) { - file.createNewFile(); - } - return file; - } catch (SecurityException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - return null; - } -} - - diff --git a/src/net/micode/notes/tool/DataUtils.java b/src/net/micode/notes/tool/DataUtils.java deleted file mode 100644 index 2a14982e..00000000 --- a/src/net/micode/notes/tool/DataUtils.java +++ /dev/null @@ -1,295 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.tool; - -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentResolver; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.OperationApplicationException; -import android.database.Cursor; -import android.os.RemoteException; -import android.util.Log; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.CallNote; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; - -import java.util.ArrayList; -import java.util.HashSet; - - -public class DataUtils { - public static final String TAG = "DataUtils"; - public static boolean batchDeleteNotes(ContentResolver resolver, HashSet ids) { - if (ids == null) { - Log.d(TAG, "the ids is null"); - return true; - } - if (ids.size() == 0) { - Log.d(TAG, "no id is in the hashset"); - return true; - } - - ArrayList operationList = new ArrayList(); - for (long id : ids) { - if(id == Notes.ID_ROOT_FOLDER) { - Log.e(TAG, "Don't delete system folder root"); - continue; - } - ContentProviderOperation.Builder builder = ContentProviderOperation - .newDelete(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); - operationList.add(builder.build()); - } - try { - ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); - if (results == null || results.length == 0 || results[0] == null) { - Log.d(TAG, "delete notes failed, ids:" + ids.toString()); - return false; - } - return true; - } catch (RemoteException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - } catch (OperationApplicationException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - } - return false; - } - - public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.PARENT_ID, desFolderId); - values.put(NoteColumns.ORIGIN_PARENT_ID, srcFolderId); - values.put(NoteColumns.LOCAL_MODIFIED, 1); - resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null); - } - - public static boolean batchMoveToFolder(ContentResolver resolver, HashSet ids, - long folderId) { - if (ids == null) { - Log.d(TAG, "the ids is null"); - return true; - } - - ArrayList operationList = new ArrayList(); - for (long id : ids) { - ContentProviderOperation.Builder builder = ContentProviderOperation - .newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id)); - builder.withValue(NoteColumns.PARENT_ID, folderId); - builder.withValue(NoteColumns.LOCAL_MODIFIED, 1); - operationList.add(builder.build()); - } - - try { - ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList); - if (results == null || results.length == 0 || results[0] == null) { - Log.d(TAG, "delete notes failed, ids:" + ids.toString()); - return false; - } - return true; - } catch (RemoteException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - } catch (OperationApplicationException e) { - Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage())); - } - return false; - } - - /** - * Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}} - */ - public static int getUserFolderCount(ContentResolver resolver) { - Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI, - new String[] { "COUNT(*)" }, - NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?", - new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)}, - null); - - int count = 0; - if(cursor != null) { - if(cursor.moveToFirst()) { - try { - count = cursor.getInt(0); - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "get folder count failed:" + e.toString()); - } finally { - cursor.close(); - } - } - } - return count; - } - - public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) { - Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), - null, - NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER, - new String [] {String.valueOf(type)}, - null); - - boolean exist = false; - if (cursor != null) { - if (cursor.getCount() > 0) { - exist = true; - } - cursor.close(); - } - return exist; - } - - public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) { - Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), - null, null, null, null); - - boolean exist = false; - if (cursor != null) { - if (cursor.getCount() > 0) { - exist = true; - } - cursor.close(); - } - return exist; - } - - public static boolean existInDataDatabase(ContentResolver resolver, long dataId) { - Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), - null, null, null, null); - - boolean exist = false; - if (cursor != null) { - if (cursor.getCount() > 0) { - exist = true; - } - cursor.close(); - } - return exist; - } - - public static boolean checkVisibleFolderName(ContentResolver resolver, String name) { - Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null, - NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + - " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + - " AND " + NoteColumns.SNIPPET + "=?", - new String[] { name }, null); - boolean exist = false; - if(cursor != null) { - if(cursor.getCount() > 0) { - exist = true; - } - cursor.close(); - } - return exist; - } - - public static HashSet getFolderNoteWidget(ContentResolver resolver, long folderId) { - Cursor c = resolver.query(Notes.CONTENT_NOTE_URI, - new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE }, - NoteColumns.PARENT_ID + "=?", - new String[] { String.valueOf(folderId) }, - null); - - HashSet set = null; - if (c != null) { - if (c.moveToFirst()) { - set = new HashSet(); - do { - try { - AppWidgetAttribute widget = new AppWidgetAttribute(); - widget.widgetId = c.getInt(0); - widget.widgetType = c.getInt(1); - set.add(widget); - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, e.toString()); - } - } while (c.moveToNext()); - } - c.close(); - } - return set; - } - - public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) { - Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, - new String [] { CallNote.PHONE_NUMBER }, - CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?", - new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE }, - null); - - if (cursor != null && cursor.moveToFirst()) { - try { - return cursor.getString(0); - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "Get call number fails " + e.toString()); - } finally { - cursor.close(); - } - } - return ""; - } - - public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) { - Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI, - new String [] { CallNote.NOTE_ID }, - CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL(" - + CallNote.PHONE_NUMBER + ",?)", - new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber }, - null); - - if (cursor != null) { - if (cursor.moveToFirst()) { - try { - return cursor.getLong(0); - } catch (IndexOutOfBoundsException e) { - Log.e(TAG, "Get call note id fails " + e.toString()); - } - } - cursor.close(); - } - return 0; - } - - public static String getSnippetById(ContentResolver resolver, long noteId) { - Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, - new String [] { NoteColumns.SNIPPET }, - NoteColumns.ID + "=?", - new String [] { String.valueOf(noteId)}, - null); - - if (cursor != null) { - String snippet = ""; - if (cursor.moveToFirst()) { - snippet = cursor.getString(0); - } - cursor.close(); - return snippet; - } - throw new IllegalArgumentException("Note is not found with id: " + noteId); - } - - public static String getFormattedSnippet(String snippet) { - if (snippet != null) { - snippet = snippet.trim(); - int index = snippet.indexOf('\n'); - if (index != -1) { - snippet = snippet.substring(0, index); - } - } - return snippet; - } -} diff --git a/src/net/micode/notes/tool/GTaskStringUtils.java b/src/net/micode/notes/tool/GTaskStringUtils.java deleted file mode 100644 index 666b7290..00000000 --- a/src/net/micode/notes/tool/GTaskStringUtils.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.tool; - -public class GTaskStringUtils { - - public final static String GTASK_JSON_ACTION_ID = "action_id"; - - public final static String GTASK_JSON_ACTION_LIST = "action_list"; - - public final static String GTASK_JSON_ACTION_TYPE = "action_type"; - - public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create"; - - public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all"; - - public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move"; - - public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update"; - - public final static String GTASK_JSON_CREATOR_ID = "creator_id"; - - public final static String GTASK_JSON_CHILD_ENTITY = "child_entity"; - - public final static String GTASK_JSON_CLIENT_VERSION = "client_version"; - - public final static String GTASK_JSON_COMPLETED = "completed"; - - public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id"; - - public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id"; - - public final static String GTASK_JSON_DELETED = "deleted"; - - public final static String GTASK_JSON_DEST_LIST = "dest_list"; - - public final static String GTASK_JSON_DEST_PARENT = "dest_parent"; - - public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type"; - - public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta"; - - public final static String GTASK_JSON_ENTITY_TYPE = "entity_type"; - - public final static String GTASK_JSON_GET_DELETED = "get_deleted"; - - public final static String GTASK_JSON_ID = "id"; - - public final static String GTASK_JSON_INDEX = "index"; - - public final static String GTASK_JSON_LAST_MODIFIED = "last_modified"; - - public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point"; - - public final static String GTASK_JSON_LIST_ID = "list_id"; - - public final static String GTASK_JSON_LISTS = "lists"; - - public final static String GTASK_JSON_NAME = "name"; - - public final static String GTASK_JSON_NEW_ID = "new_id"; - - public final static String GTASK_JSON_NOTES = "notes"; - - public final static String GTASK_JSON_PARENT_ID = "parent_id"; - - public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id"; - - public final static String GTASK_JSON_RESULTS = "results"; - - public final static String GTASK_JSON_SOURCE_LIST = "source_list"; - - public final static String GTASK_JSON_TASKS = "tasks"; - - public final static String GTASK_JSON_TYPE = "type"; - - public final static String GTASK_JSON_TYPE_GROUP = "GROUP"; - - public final static String GTASK_JSON_TYPE_TASK = "TASK"; - - public final static String GTASK_JSON_USER = "user"; - - public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]"; - - public final static String FOLDER_DEFAULT = "Default"; - - public final static String FOLDER_CALL_NOTE = "Call_Note"; - - public final static String FOLDER_META = "METADATA"; - - public final static String META_HEAD_GTASK_ID = "meta_gid"; - - public final static String META_HEAD_NOTE = "meta_note"; - - public final static String META_HEAD_DATA = "meta_data"; - - public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE"; - -} diff --git a/src/net/micode/notes/tool/ResourceParser.java b/src/net/micode/notes/tool/ResourceParser.java deleted file mode 100644 index 1ad3ad60..00000000 --- a/src/net/micode/notes/tool/ResourceParser.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.tool; - -import android.content.Context; -import android.preference.PreferenceManager; - -import net.micode.notes.R; -import net.micode.notes.ui.NotesPreferenceActivity; - -public class ResourceParser { - - public static final int YELLOW = 0; - public static final int BLUE = 1; - public static final int WHITE = 2; - public static final int GREEN = 3; - public static final int RED = 4; - - public static final int BG_DEFAULT_COLOR = YELLOW; - - public static final int TEXT_SMALL = 0; - public static final int TEXT_MEDIUM = 1; - public static final int TEXT_LARGE = 2; - public static final int TEXT_SUPER = 3; - - public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM; - - public static class NoteBgResources { - private final static int [] BG_EDIT_RESOURCES = new int [] { - R.drawable.edit_yellow, - R.drawable.edit_blue, - R.drawable.edit_white, - R.drawable.edit_green, - R.drawable.edit_red - }; - - private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] { - R.drawable.edit_title_yellow, - R.drawable.edit_title_blue, - R.drawable.edit_title_white, - R.drawable.edit_title_green, - R.drawable.edit_title_red - }; - - public static int getNoteBgResource(int id) { - return BG_EDIT_RESOURCES[id]; - } - - public static int getNoteTitleBgResource(int id) { - return BG_EDIT_TITLE_RESOURCES[id]; - } - } - - public static int getDefaultBgId(Context context) { - if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean( - NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) { - return (int) (Math.random() * NoteBgResources.BG_EDIT_RESOURCES.length); - } else { - return BG_DEFAULT_COLOR; - } - } - - public static class NoteItemBgResources { - private final static int [] BG_FIRST_RESOURCES = new int [] { - R.drawable.list_yellow_up, - R.drawable.list_blue_up, - R.drawable.list_white_up, - R.drawable.list_green_up, - R.drawable.list_red_up - }; - - private final static int [] BG_NORMAL_RESOURCES = new int [] { - R.drawable.list_yellow_middle, - R.drawable.list_blue_middle, - R.drawable.list_white_middle, - R.drawable.list_green_middle, - R.drawable.list_red_middle - }; - - private final static int [] BG_LAST_RESOURCES = new int [] { - R.drawable.list_yellow_down, - R.drawable.list_blue_down, - R.drawable.list_white_down, - R.drawable.list_green_down, - R.drawable.list_red_down, - }; - - private final static int [] BG_SINGLE_RESOURCES = new int [] { - R.drawable.list_yellow_single, - R.drawable.list_blue_single, - R.drawable.list_white_single, - R.drawable.list_green_single, - R.drawable.list_red_single - }; - - public static int getNoteBgFirstRes(int id) { - return BG_FIRST_RESOURCES[id]; - } - - public static int getNoteBgLastRes(int id) { - return BG_LAST_RESOURCES[id]; - } - - public static int getNoteBgSingleRes(int id) { - return BG_SINGLE_RESOURCES[id]; - } - - public static int getNoteBgNormalRes(int id) { - return BG_NORMAL_RESOURCES[id]; - } - - public static int getFolderBgRes() { - return R.drawable.list_folder; - } - } - - public static class WidgetBgResources { - private final static int [] BG_2X_RESOURCES = new int [] { - R.drawable.widget_2x_yellow, - R.drawable.widget_2x_blue, - R.drawable.widget_2x_white, - R.drawable.widget_2x_green, - R.drawable.widget_2x_red, - }; - - public static int getWidget2xBgResource(int id) { - return BG_2X_RESOURCES[id]; - } - - private final static int [] BG_4X_RESOURCES = new int [] { - R.drawable.widget_4x_yellow, - R.drawable.widget_4x_blue, - R.drawable.widget_4x_white, - R.drawable.widget_4x_green, - R.drawable.widget_4x_red - }; - - public static int getWidget4xBgResource(int id) { - return BG_4X_RESOURCES[id]; - } - } - - public static class TextAppearanceResources { - private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] { - R.style.TextAppearanceNormal, - R.style.TextAppearanceMedium, - R.style.TextAppearanceLarge, - R.style.TextAppearanceSuper - }; - - public static int getTexAppearanceResource(int id) { - /** - * HACKME: Fix bug of store the resource id in shared preference. - * The id may larger than the length of resources, in this case, - * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} - */ - if (id >= TEXTAPPEARANCE_RESOURCES.length) { - return BG_DEFAULT_FONT_SIZE; - } - return TEXTAPPEARANCE_RESOURCES[id]; - } - - public static int getResourcesSize() { - return TEXTAPPEARANCE_RESOURCES.length; - } - } -} diff --git a/src/net/micode/notes/ui/AlarmAlertActivity.java b/src/net/micode/notes/ui/AlarmAlertActivity.java deleted file mode 100644 index 85723be0..00000000 --- a/src/net/micode/notes/ui/AlarmAlertActivity.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.DialogInterface.OnDismissListener; -import android.content.Intent; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.PowerManager; -import android.provider.Settings; -import android.view.Window; -import android.view.WindowManager; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.DataUtils; - -import java.io.IOException; - - -public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { - private long mNoteId; - private String mSnippet; - private static final int SNIPPET_PREW_MAX_LEN = 60; - MediaPlayer mPlayer; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - - final Window win = getWindow(); - win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - - if (!isScreenOn()) { - win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); - } - - Intent intent = getIntent(); - - try { - mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); - mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); - mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, - SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) - : mSnippet; - } catch (IllegalArgumentException e) { - e.printStackTrace(); - return; - } - - mPlayer = new MediaPlayer(); - if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { - showActionDialog(); - playAlarmSound(); - } else { - finish(); - } - } - - private boolean isScreenOn() { - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - return pm.isScreenOn(); - } - - private void playAlarmSound() { - Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); - - int silentModeStreams = Settings.System.getInt(getContentResolver(), - Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); - - if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { - mPlayer.setAudioStreamType(silentModeStreams); - } else { - mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - } - try { - mPlayer.setDataSource(this, url); - mPlayer.prepare(); - mPlayer.setLooping(true); - mPlayer.start(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalStateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - private void showActionDialog() { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle(R.string.app_name); - dialog.setMessage(mSnippet); - dialog.setPositiveButton(R.string.notealert_ok, this); - if (isScreenOn()) { - dialog.setNegativeButton(R.string.notealert_enter, this); - } - dialog.show().setOnDismissListener(this); - } - - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_NEGATIVE: - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, mNoteId); - startActivity(intent); - break; - default: - break; - } - } - - public void onDismiss(DialogInterface dialog) { - stopAlarmSound(); - finish(); - } - - private void stopAlarmSound() { - if (mPlayer != null) { - mPlayer.stop(); - mPlayer.release(); - mPlayer = null; - } - } -} diff --git a/src/net/micode/notes/ui/AlarmInitReceiver.java b/src/net/micode/notes/ui/AlarmInitReceiver.java deleted file mode 100644 index f2212025..00000000 --- a/src/net/micode/notes/ui/AlarmInitReceiver.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; - - -public class AlarmInitReceiver extends BroadcastReceiver { - - private static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE - }; - - private static final int COLUMN_ID = 0; - private static final int COLUMN_ALERTED_DATE = 1; - - @Override - public void onReceive(Context context, Intent intent) { - long currentDate = System.currentTimeMillis(); - Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, - PROJECTION, - NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, - new String[] { String.valueOf(currentDate) }, - null); - - if (c != null) { - if (c.moveToFirst()) { - do { - long alertDate = c.getLong(COLUMN_ALERTED_DATE); - Intent sender = new Intent(context, AlarmReceiver.class); - sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); - AlarmManager alermManager = (AlarmManager) context - .getSystemService(Context.ALARM_SERVICE); - alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); - } while (c.moveToNext()); - } - c.close(); - } - } -} diff --git a/src/net/micode/notes/ui/DateTimePicker.java b/src/net/micode/notes/ui/DateTimePicker.java deleted file mode 100644 index 496b0cda..00000000 --- a/src/net/micode/notes/ui/DateTimePicker.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import java.text.DateFormatSymbols; -import java.util.Calendar; - -import net.micode.notes.R; - - -import android.content.Context; -import android.text.format.DateFormat; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.NumberPicker; - -public class DateTimePicker extends FrameLayout { - - private static final boolean DEFAULT_ENABLE_STATE = true; - - private static final int HOURS_IN_HALF_DAY = 12; - private static final int HOURS_IN_ALL_DAY = 24; - private static final int DAYS_IN_ALL_WEEK = 7; - private static final int DATE_SPINNER_MIN_VAL = 0; - private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; - private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; - private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; - private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; - private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; - private static final int MINUT_SPINNER_MIN_VAL = 0; - private static final int MINUT_SPINNER_MAX_VAL = 59; - private static final int AMPM_SPINNER_MIN_VAL = 0; - private static final int AMPM_SPINNER_MAX_VAL = 1; - - private final NumberPicker mDateSpinner; - private final NumberPicker mHourSpinner; - private final NumberPicker mMinuteSpinner; - private final NumberPicker mAmPmSpinner; - private Calendar mDate; - - private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; - - private boolean mIsAm; - - private boolean mIs24HourView; - - private boolean mIsEnabled = DEFAULT_ENABLE_STATE; - - private boolean mInitialising; - - private OnDateTimeChangedListener mOnDateTimeChangedListener; - - private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { - @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); - updateDateControl(); - onDateTimeChanged(); - } - }; - - private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { - @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - boolean isDateChanged = false; - Calendar cal = Calendar.getInstance(); - if (!mIs24HourView) { - if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, 1); - isDateChanged = true; - } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, -1); - isDateChanged = true; - } - if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || - oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { - mIsAm = !mIsAm; - updateAmPmControl(); - } - } else { - if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, 1); - isDateChanged = true; - } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, -1); - isDateChanged = true; - } - } - int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); - mDate.set(Calendar.HOUR_OF_DAY, newHour); - onDateTimeChanged(); - if (isDateChanged) { - setCurrentYear(cal.get(Calendar.YEAR)); - setCurrentMonth(cal.get(Calendar.MONTH)); - setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); - } - } - }; - - private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { - @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - int minValue = mMinuteSpinner.getMinValue(); - int maxValue = mMinuteSpinner.getMaxValue(); - int offset = 0; - if (oldVal == maxValue && newVal == minValue) { - offset += 1; - } else if (oldVal == minValue && newVal == maxValue) { - offset -= 1; - } - if (offset != 0) { - mDate.add(Calendar.HOUR_OF_DAY, offset); - mHourSpinner.setValue(getCurrentHour()); - updateDateControl(); - int newHour = getCurrentHourOfDay(); - if (newHour >= HOURS_IN_HALF_DAY) { - mIsAm = false; - updateAmPmControl(); - } else { - mIsAm = true; - updateAmPmControl(); - } - } - mDate.set(Calendar.MINUTE, newVal); - onDateTimeChanged(); - } - }; - - private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { - @Override - public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - mIsAm = !mIsAm; - if (mIsAm) { - mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); - } else { - mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); - } - updateAmPmControl(); - onDateTimeChanged(); - } - }; - - public interface OnDateTimeChangedListener { - void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute); - } - - public DateTimePicker(Context context) { - this(context, System.currentTimeMillis()); - } - - public DateTimePicker(Context context, long date) { - this(context, date, DateFormat.is24HourFormat(context)); - } - - public DateTimePicker(Context context, long date, boolean is24HourView) { - super(context); - mDate = Calendar.getInstance(); - mInitialising = true; - mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; - inflate(context, R.layout.datetime_picker, this); - - mDateSpinner = (NumberPicker) findViewById(R.id.date); - mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); - mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); - mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); - - mHourSpinner = (NumberPicker) findViewById(R.id.hour); - mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); - mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); - mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); - mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); - mMinuteSpinner.setOnLongPressUpdateInterval(100); - mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); - - String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); - mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); - mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); - mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); - mAmPmSpinner.setDisplayedValues(stringsForAmPm); - mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - - // update controls to initial state - updateDateControl(); - updateHourControl(); - updateAmPmControl(); - - set24HourView(is24HourView); - - // set to current time - setCurrentDate(date); - - setEnabled(isEnabled()); - - // set the content descriptions - mInitialising = false; - } - - @Override - public void setEnabled(boolean enabled) { - if (mIsEnabled == enabled) { - return; - } - super.setEnabled(enabled); - mDateSpinner.setEnabled(enabled); - mMinuteSpinner.setEnabled(enabled); - mHourSpinner.setEnabled(enabled); - mAmPmSpinner.setEnabled(enabled); - mIsEnabled = enabled; - } - - @Override - public boolean isEnabled() { - return mIsEnabled; - } - - /** - * Get the current date in millis - * - * @return the current date in millis - */ - public long getCurrentDateInTimeMillis() { - return mDate.getTimeInMillis(); - } - - /** - * Set the current date - * - * @param date The current date in millis - */ - public void setCurrentDate(long date) { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(date); - setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), - cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); - } - - /** - * Set the current date - * - * @param year The current year - * @param month The current month - * @param dayOfMonth The current dayOfMonth - * @param hourOfDay The current hourOfDay - * @param minute The current minute - */ - public void setCurrentDate(int year, int month, - int dayOfMonth, int hourOfDay, int minute) { - setCurrentYear(year); - setCurrentMonth(month); - setCurrentDay(dayOfMonth); - setCurrentHour(hourOfDay); - setCurrentMinute(minute); - } - - /** - * Get current year - * - * @return The current year - */ - public int getCurrentYear() { - return mDate.get(Calendar.YEAR); - } - - /** - * Set current year - * - * @param year The current year - */ - public void setCurrentYear(int year) { - if (!mInitialising && year == getCurrentYear()) { - return; - } - mDate.set(Calendar.YEAR, year); - updateDateControl(); - onDateTimeChanged(); - } - - /** - * Get current month in the year - * - * @return The current month in the year - */ - public int getCurrentMonth() { - return mDate.get(Calendar.MONTH); - } - - /** - * Set current month in the year - * - * @param month The month in the year - */ - public void setCurrentMonth(int month) { - if (!mInitialising && month == getCurrentMonth()) { - return; - } - mDate.set(Calendar.MONTH, month); - updateDateControl(); - onDateTimeChanged(); - } - - /** - * Get current day of the month - * - * @return The day of the month - */ - public int getCurrentDay() { - return mDate.get(Calendar.DAY_OF_MONTH); - } - - /** - * Set current day of the month - * - * @param dayOfMonth The day of the month - */ - public void setCurrentDay(int dayOfMonth) { - if (!mInitialising && dayOfMonth == getCurrentDay()) { - return; - } - mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); - updateDateControl(); - onDateTimeChanged(); - } - - /** - * Get current hour in 24 hour mode, in the range (0~23) - * @return The current hour in 24 hour mode - */ - public int getCurrentHourOfDay() { - return mDate.get(Calendar.HOUR_OF_DAY); - } - - private int getCurrentHour() { - if (mIs24HourView){ - return getCurrentHourOfDay(); - } else { - int hour = getCurrentHourOfDay(); - if (hour > HOURS_IN_HALF_DAY) { - return hour - HOURS_IN_HALF_DAY; - } else { - return hour == 0 ? HOURS_IN_HALF_DAY : hour; - } - } - } - - /** - * Set current hour in 24 hour mode, in the range (0~23) - * - * @param hourOfDay - */ - public void setCurrentHour(int hourOfDay) { - if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { - return; - } - mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); - if (!mIs24HourView) { - if (hourOfDay >= HOURS_IN_HALF_DAY) { - mIsAm = false; - if (hourOfDay > HOURS_IN_HALF_DAY) { - hourOfDay -= HOURS_IN_HALF_DAY; - } - } else { - mIsAm = true; - if (hourOfDay == 0) { - hourOfDay = HOURS_IN_HALF_DAY; - } - } - updateAmPmControl(); - } - mHourSpinner.setValue(hourOfDay); - onDateTimeChanged(); - } - - /** - * Get currentMinute - * - * @return The Current Minute - */ - public int getCurrentMinute() { - return mDate.get(Calendar.MINUTE); - } - - /** - * Set current minute - */ - public void setCurrentMinute(int minute) { - if (!mInitialising && minute == getCurrentMinute()) { - return; - } - mMinuteSpinner.setValue(minute); - mDate.set(Calendar.MINUTE, minute); - onDateTimeChanged(); - } - - /** - * @return true if this is in 24 hour view else false. - */ - public boolean is24HourView () { - return mIs24HourView; - } - - /** - * Set whether in 24 hour or AM/PM mode. - * - * @param is24HourView True for 24 hour mode. False for AM/PM mode. - */ - public void set24HourView(boolean is24HourView) { - if (mIs24HourView == is24HourView) { - return; - } - mIs24HourView = is24HourView; - mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); - int hour = getCurrentHourOfDay(); - updateHourControl(); - setCurrentHour(hour); - updateAmPmControl(); - } - - private void updateDateControl() { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); - mDateSpinner.setDisplayedValues(null); - for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { - cal.add(Calendar.DAY_OF_YEAR, 1); - mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); - } - mDateSpinner.setDisplayedValues(mDateDisplayValues); - mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); - mDateSpinner.invalidate(); - } - - private void updateAmPmControl() { - if (mIs24HourView) { - mAmPmSpinner.setVisibility(View.GONE); - } else { - int index = mIsAm ? Calendar.AM : Calendar.PM; - mAmPmSpinner.setValue(index); - mAmPmSpinner.setVisibility(View.VISIBLE); - } - } - - private void updateHourControl() { - if (mIs24HourView) { - mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); - mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); - } else { - mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); - mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); - } - } - - /** - * Set the callback that indicates the 'Set' button has been pressed. - * @param callback the callback, if null will do nothing - */ - public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { - mOnDateTimeChangedListener = callback; - } - - private void onDateTimeChanged() { - if (mOnDateTimeChangedListener != null) { - mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), - getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); - } - } -} diff --git a/src/net/micode/notes/ui/DateTimePickerDialog.java b/src/net/micode/notes/ui/DateTimePickerDialog.java deleted file mode 100644 index 2c47ba41..00000000 --- a/src/net/micode/notes/ui/DateTimePickerDialog.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import java.util.Calendar; - -import net.micode.notes.R; -import net.micode.notes.ui.DateTimePicker; -import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.text.format.DateFormat; -import android.text.format.DateUtils; - -public class DateTimePickerDialog extends AlertDialog implements OnClickListener { - - private Calendar mDate = Calendar.getInstance(); - private boolean mIs24HourView; - private OnDateTimeSetListener mOnDateTimeSetListener; - private DateTimePicker mDateTimePicker; - - public interface OnDateTimeSetListener { - void OnDateTimeSet(AlertDialog dialog, long date); - } - - public DateTimePickerDialog(Context context, long date) { - super(context); - mDateTimePicker = new DateTimePicker(context); - setView(mDateTimePicker); - mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { - public void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute) { - mDate.set(Calendar.YEAR, year); - mDate.set(Calendar.MONTH, month); - mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); - mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); - mDate.set(Calendar.MINUTE, minute); - updateTitle(mDate.getTimeInMillis()); - } - }); - mDate.setTimeInMillis(date); - mDate.set(Calendar.SECOND, 0); - mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); - setButton(context.getString(R.string.datetime_dialog_ok), this); - setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); - set24HourView(DateFormat.is24HourFormat(this.getContext())); - updateTitle(mDate.getTimeInMillis()); - } - - public void set24HourView(boolean is24HourView) { - mIs24HourView = is24HourView; - } - - public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { - mOnDateTimeSetListener = callBack; - } - - private void updateTitle(long date) { - int flag = - DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME; - flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; - setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); - } - - public void onClick(DialogInterface arg0, int arg1) { - if (mOnDateTimeSetListener != null) { - mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); - } - } - -} \ No newline at end of file diff --git a/src/net/micode/notes/ui/DropdownMenu.java b/src/net/micode/notes/ui/DropdownMenu.java deleted file mode 100644 index 613dc74a..00000000 --- a/src/net/micode/notes/ui/DropdownMenu.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.content.Context; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.PopupMenu; -import android.widget.PopupMenu.OnMenuItemClickListener; - -import net.micode.notes.R; - -public class DropdownMenu { - private Button mButton; - private PopupMenu mPopupMenu; - private Menu mMenu; - - public DropdownMenu(Context context, Button button, int menuId) { - mButton = button; - mButton.setBackgroundResource(R.drawable.dropdown_icon); - mPopupMenu = new PopupMenu(context, mButton); - mMenu = mPopupMenu.getMenu(); - mPopupMenu.getMenuInflater().inflate(menuId, mMenu); - mButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mPopupMenu.show(); - } - }); - } - - public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { - if (mPopupMenu != null) { - mPopupMenu.setOnMenuItemClickListener(listener); - } - } - - public MenuItem findItem(int id) { - return mMenu.findItem(id); - } - - public void setTitle(CharSequence title) { - mButton.setText(title); - } -} diff --git a/src/net/micode/notes/ui/FoldersListAdapter.java b/src/net/micode/notes/ui/FoldersListAdapter.java deleted file mode 100644 index 96b77da2..00000000 --- a/src/net/micode/notes/ui/FoldersListAdapter.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.content.Context; -import android.database.Cursor; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; -import android.widget.LinearLayout; -import android.widget.TextView; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; - - -public class FoldersListAdapter extends CursorAdapter { - public static final String [] PROJECTION = { - NoteColumns.ID, - NoteColumns.SNIPPET - }; - - public static final int ID_COLUMN = 0; - public static final int NAME_COLUMN = 1; - - public FoldersListAdapter(Context context, Cursor c) { - super(context, c); - // TODO Auto-generated constructor stub - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return new FolderListItem(context); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - if (view instanceof FolderListItem) { - String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context - .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); - ((FolderListItem) view).bind(folderName); - } - } - - public String getFolderName(Context context, int position) { - Cursor cursor = (Cursor) getItem(position); - return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context - .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); - } - - private class FolderListItem extends LinearLayout { - private TextView mName; - - public FolderListItem(Context context) { - super(context); - inflate(context, R.layout.folder_list_item, this); - mName = (TextView) findViewById(R.id.tv_folder_name); - } - - public void bind(String name) { - mName.setText(name); - } - } - -} diff --git a/src/net/micode/notes/ui/NoteEditActivity.java b/src/net/micode/notes/ui/NoteEditActivity.java deleted file mode 100644 index 96a9ff89..00000000 --- a/src/net/micode/notes/ui/NoteEditActivity.java +++ /dev/null @@ -1,873 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.app.Activity; -import android.app.AlarmManager; -import android.app.AlertDialog; -import android.app.PendingIntent; -import android.app.SearchManager; -import android.appwidget.AppWidgetManager; -import android.content.ContentUris; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.graphics.Paint; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.TextUtils; -import android.text.format.DateUtils; -import android.text.style.BackgroundColorSpan; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.WindowManager; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.CompoundButton.OnCheckedChangeListener; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; -import android.widget.Toast; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.TextNote; -import net.micode.notes.model.WorkingNote; -import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; -import net.micode.notes.tool.DataUtils; -import net.micode.notes.tool.ResourceParser; -import net.micode.notes.tool.ResourceParser.TextAppearanceResources; -import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; -import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; -import net.micode.notes.widget.NoteWidgetProvider_2x; -import net.micode.notes.widget.NoteWidgetProvider_4x; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - - -public class NoteEditActivity extends Activity implements OnClickListener, - NoteSettingChangedListener, OnTextViewChangeListener { - private class HeadViewHolder { - public TextView tvModified; - - public ImageView ivAlertIcon; - - public TextView tvAlertDate; - - public ImageView ibSetBgColor; - } - - private static final Map sBgSelectorBtnsMap = new HashMap(); - static { - sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); - sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); - sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); - sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); - sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); - } - - private static final Map sBgSelectorSelectionMap = new HashMap(); - static { - sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); - sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); - sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); - sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); - sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); - } - - private static final Map sFontSizeBtnsMap = new HashMap(); - static { - sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); - sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); - sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); - sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); - } - - private static final Map sFontSelectorSelectionMap = new HashMap(); - static { - sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); - sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); - sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); - sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); - } - - private static final String TAG = "NoteEditActivity"; - - private HeadViewHolder mNoteHeaderHolder; - - private View mHeadViewPanel; - - private View mNoteBgColorSelector; - - private View mFontSizeSelector; - - private EditText mNoteEditor; - - private View mNoteEditorPanel; - - private WorkingNote mWorkingNote; - - private SharedPreferences mSharedPrefs; - private int mFontSizeId; - - private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; - - private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; - - public static final String TAG_CHECKED = String.valueOf('\u221A'); - public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); - - private LinearLayout mEditTextList; - - private String mUserQuery; - private Pattern mPattern; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - this.setContentView(R.layout.note_edit); - - if (savedInstanceState == null && !initActivityState(getIntent())) { - finish(); - return; - } - initResources(); - } - - /** - * Current activity may be killed when the memory is low. Once it is killed, for another time - * user load this activity, we should restore the former state - */ - @Override - protected void onRestoreInstanceState(Bundle savedInstanceState) { - super.onRestoreInstanceState(savedInstanceState); - if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); - if (!initActivityState(intent)) { - finish(); - return; - } - Log.d(TAG, "Restoring from killed activity"); - } - } - - private boolean initActivityState(Intent intent) { - /** - * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, - * then jump to the NotesListActivity - */ - mWorkingNote = null; - if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { - long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); - mUserQuery = ""; - - /** - * Starting from the searched result - */ - if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { - noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); - mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); - } - - if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { - Intent jump = new Intent(this, NotesListActivity.class); - startActivity(jump); - showToast(R.string.error_note_not_exist); - finish(); - return false; - } else { - mWorkingNote = WorkingNote.load(this, noteId); - if (mWorkingNote == null) { - Log.e(TAG, "load note failed with note id" + noteId); - finish(); - return false; - } - } - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN - | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); - } else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { - // New note - long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); - int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID); - int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, - Notes.TYPE_WIDGET_INVALIDE); - int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, - ResourceParser.getDefaultBgId(this)); - - // Parse call-record note - String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); - long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); - if (callDate != 0 && phoneNumber != null) { - if (TextUtils.isEmpty(phoneNumber)) { - Log.w(TAG, "The call record number is null"); - } - long noteId = 0; - if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), - phoneNumber, callDate)) > 0) { - mWorkingNote = WorkingNote.load(this, noteId); - if (mWorkingNote == null) { - Log.e(TAG, "load call note failed with note id" + noteId); - finish(); - return false; - } - } else { - mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, - widgetType, bgResId); - mWorkingNote.convertToCallNote(phoneNumber, callDate); - } - } else { - mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, - bgResId); - } - - getWindow().setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE - | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); - } else { - Log.e(TAG, "Intent not specified action, should not support"); - finish(); - return false; - } - mWorkingNote.setOnSettingStatusChangedListener(this); - return true; - } - - @Override - protected void onResume() { - super.onResume(); - initNoteScreen(); - } - - private void initNoteScreen() { - mNoteEditor.setTextAppearance(this, TextAppearanceResources - .getTexAppearanceResource(mFontSizeId)); - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - switchToListMode(mWorkingNote.getContent()); - } else { - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mNoteEditor.setSelection(mNoteEditor.getText().length()); - } - for (Integer id : sBgSelectorSelectionMap.keySet()) { - findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); - } - mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); - mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); - - mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this, - mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE - | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_YEAR)); - - /** - * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker - * is not ready - */ - showAlertHeader(); - } - - private void showAlertHeader() { - if (mWorkingNote.hasClockAlert()) { - long time = System.currentTimeMillis(); - if (time > mWorkingNote.getAlertDate()) { - mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); - } else { - mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( - mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS)); - } - mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); - mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); - } else { - mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); - mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); - }; - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - initActivityState(intent); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - /** - * For new note without note id, we should firstly save it to - * generate a id. If the editing note is not worth saving, there - * is no id which is equivalent to create new note - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (mNoteBgColorSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mNoteBgColorSelector, ev)) { - mNoteBgColorSelector.setVisibility(View.GONE); - return true; - } - - if (mFontSizeSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mFontSizeSelector, ev)) { - mFontSizeSelector.setVisibility(View.GONE); - return true; - } - return super.dispatchTouchEvent(ev); - } - - private boolean inRangeOfView(View view, MotionEvent ev) { - int []location = new int[2]; - view.getLocationOnScreen(location); - int x = location[0]; - int y = location[1]; - if (ev.getX() < x - || ev.getX() > (x + view.getWidth()) - || ev.getY() < y - || ev.getY() > (y + view.getHeight())) { - return false; - } - return true; - } - - private void initResources() { - mHeadViewPanel = findViewById(R.id.note_title); - mNoteHeaderHolder = new HeadViewHolder(); - mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); - mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); - mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); - mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); - mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); - mNoteEditor = (EditText) findViewById(R.id.note_edit_view); - mNoteEditorPanel = findViewById(R.id.sv_note_edit); - mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); - for (int id : sBgSelectorBtnsMap.keySet()) { - ImageView iv = (ImageView) findViewById(id); - iv.setOnClickListener(this); - } - - mFontSizeSelector = findViewById(R.id.font_size_selector); - for (int id : sFontSizeBtnsMap.keySet()) { - View view = findViewById(id); - view.setOnClickListener(this); - }; - mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); - mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); - /** - * HACKME: Fix bug of store the resource id in shared preference. - * The id may larger than the length of resources, in this case, - * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} - */ - if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { - mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; - } - mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); - } - - @Override - protected void onPause() { - super.onPause(); - if(saveNote()) { - Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); - } - clearSettingState(); - } - - private void updateWidget() { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { - intent.setClass(this, NoteWidgetProvider_2x.class); - } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { - intent.setClass(this, NoteWidgetProvider_4x.class); - } else { - Log.e(TAG, "Unspported widget type"); - return; - } - - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - mWorkingNote.getWidgetId() - }); - - sendBroadcast(intent); - setResult(RESULT_OK, intent); - } - - public void onClick(View v) { - int id = v.getId(); - if (id == R.id.btn_set_bg_color) { - mNoteBgColorSelector.setVisibility(View.VISIBLE); - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - - View.VISIBLE); - } else if (sBgSelectorBtnsMap.containsKey(id)) { - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.GONE); - mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); - mNoteBgColorSelector.setVisibility(View.GONE); - } else if (sFontSizeBtnsMap.containsKey(id)) { - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); - mFontSizeId = sFontSizeBtnsMap.get(id); - mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - getWorkingText(); - switchToListMode(mWorkingNote.getContent()); - } else { - mNoteEditor.setTextAppearance(this, - TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); - } - mFontSizeSelector.setVisibility(View.GONE); - } - } - - @Override - public void onBackPressed() { - if(clearSettingState()) { - return; - } - - saveNote(); - super.onBackPressed(); - } - - private boolean clearSettingState() { - if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { - mNoteBgColorSelector.setVisibility(View.GONE); - return true; - } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { - mFontSizeSelector.setVisibility(View.GONE); - return true; - } - return false; - } - - public void onBackgroundColorChanged() { - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.VISIBLE); - mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); - mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - if (isFinishing()) { - return true; - } - clearSettingState(); - menu.clear(); - if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { - getMenuInflater().inflate(R.menu.call_note_edit, menu); - } else { - getMenuInflater().inflate(R.menu.note_edit, menu); - } - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); - } else { - menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); - } - if (mWorkingNote.hasClockAlert()) { - menu.findItem(R.id.menu_alert).setVisible(false); - } else { - menu.findItem(R.id.menu_delete_remind).setVisible(false); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_new_note: - createNewNote(); - break; - case R.id.menu_delete: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.alert_title_delete)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setMessage(getString(R.string.alert_message_delete_note)); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - deleteCurrentNote(); - finish(); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - break; - case R.id.menu_font_size: - mFontSizeSelector.setVisibility(View.VISIBLE); - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); - break; - case R.id.menu_list_mode: - mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? - TextNote.MODE_CHECK_LIST : 0); - break; - case R.id.menu_share: - getWorkingText(); - sendTo(this, mWorkingNote.getContent()); - break; - case R.id.menu_send_to_desktop: - sendToDesktop(); - break; - case R.id.menu_alert: - setReminder(); - break; - case R.id.menu_delete_remind: - mWorkingNote.setAlertDate(0, false); - break; - default: - break; - } - return true; - } - - private void setReminder() { - DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); - d.setOnDateTimeSetListener(new OnDateTimeSetListener() { - public void OnDateTimeSet(AlertDialog dialog, long date) { - mWorkingNote.setAlertDate(date , true); - } - }); - d.show(); - } - - /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type - */ - private void sendTo(Context context, String info) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_TEXT, info); - intent.setType("text/plain"); - context.startActivity(intent); - } - - private void createNewNote() { - // Firstly, save current editing notes - saveNote(); - - // For safety, start a new NoteEditActivity - finish(); - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); - startActivity(intent); - } - - private void deleteCurrentNote() { - if (mWorkingNote.existInDatabase()) { - HashSet ids = new HashSet(); - long id = mWorkingNote.getNoteId(); - if (id != Notes.ID_ROOT_FOLDER) { - ids.add(id); - } else { - Log.d(TAG, "Wrong note id, should not happen"); - } - if (!isSyncMode()) { - if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { - Log.e(TAG, "Delete Note error"); - } - } else { - if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { - Log.e(TAG, "Move notes to trash folder error, should not happens"); - } - } - } - mWorkingNote.markDeleted(true); - } - - private boolean isSyncMode() { - return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; - } - - public void onClockAlertChanged(long date, boolean set) { - /** - * User could set clock to an unsaved note, so before setting the - * alert clock, we should save the note first - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - if (mWorkingNote.getNoteId() > 0) { - Intent intent = new Intent(this, AlarmReceiver.class); - intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); - AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); - showAlertHeader(); - if(!set) { - alarmManager.cancel(pendingIntent); - } else { - alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); - } - } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ - Log.e(TAG, "Clock alert setting error"); - showToast(R.string.error_note_empty_for_clock); - } - } - - public void onWidgetChanged() { - updateWidget(); - } - - public void onEditTextDelete(int index, String text) { - int childCount = mEditTextList.getChildCount(); - if (childCount == 1) { - return; - } - - for (int i = index + 1; i < childCount; i++) { - ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) - .setIndex(i - 1); - } - - mEditTextList.removeViewAt(index); - NoteEditText edit = null; - if(index == 0) { - edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( - R.id.et_edit_text); - } else { - edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( - R.id.et_edit_text); - } - int length = edit.length(); - edit.append(text); - edit.requestFocus(); - edit.setSelection(length); - } - - public void onEditTextEnter(int index, String text) { - /** - * Should not happen, check for debug - */ - if(index > mEditTextList.getChildCount()) { - Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); - } - - View view = getListItem(text, index); - mEditTextList.addView(view, index); - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.requestFocus(); - edit.setSelection(0); - for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { - ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) - .setIndex(i); - } - } - - private void switchToListMode(String text) { - mEditTextList.removeAllViews(); - String[] items = text.split("\n"); - int index = 0; - for (String item : items) { - if(!TextUtils.isEmpty(item)) { - mEditTextList.addView(getListItem(item, index)); - index++; - } - } - mEditTextList.addView(getListItem("", index)); - mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); - - mNoteEditor.setVisibility(View.GONE); - mEditTextList.setVisibility(View.VISIBLE); - } - - private Spannable getHighlightQueryResult(String fullText, String userQuery) { - SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); - if (!TextUtils.isEmpty(userQuery)) { - mPattern = Pattern.compile(userQuery); - Matcher m = mPattern.matcher(fullText); - int start = 0; - while (m.find(start)) { - spannable.setSpan( - new BackgroundColorSpan(this.getResources().getColor( - R.color.user_query_highlight)), m.start(), m.end(), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - start = m.end(); - } - } - return spannable; - } - - private View getListItem(String item, int index) { - View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); - final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); - CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); - cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else { - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - } - } - }); - - if (item.startsWith(TAG_CHECKED)) { - cb.setChecked(true); - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - item = item.substring(TAG_CHECKED.length(), item.length()).trim(); - } else if (item.startsWith(TAG_UNCHECKED)) { - cb.setChecked(false); - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); - } - - edit.setOnTextViewChangeListener(this); - edit.setIndex(index); - edit.setText(getHighlightQueryResult(item, mUserQuery)); - return view; - } - - public void onTextChange(int index, boolean hasText) { - if (index >= mEditTextList.getChildCount()) { - Log.e(TAG, "Wrong index, should not happen"); - return; - } - if(hasText) { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); - } else { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); - } - } - - public void onCheckListModeChanged(int oldMode, int newMode) { - if (newMode == TextNote.MODE_CHECK_LIST) { - switchToListMode(mNoteEditor.getText().toString()); - } else { - if (!getWorkingText()) { - mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", - "")); - } - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mEditTextList.setVisibility(View.GONE); - mNoteEditor.setVisibility(View.VISIBLE); - } - } - - private boolean getWorkingText() { - boolean hasChecked = false; - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mEditTextList.getChildCount(); i++) { - View view = mEditTextList.getChildAt(i); - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - if (!TextUtils.isEmpty(edit.getText())) { - if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { - sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); - hasChecked = true; - } else { - sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); - } - } - } - mWorkingNote.setWorkingText(sb.toString()); - } else { - mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); - } - return hasChecked; - } - - private boolean saveNote() { - getWorkingText(); - boolean saved = mWorkingNote.saveNote(); - if (saved) { - /** - * There are two modes from List view to edit view, open one note, - * create/edit a node. Opening node requires to the original - * position in the list when back from edit view, while creating a - * new node requires to the top of the list. This code - * {@link #RESULT_OK} is used to identify the create/edit state - */ - setResult(RESULT_OK); - } - return saved; - } - - private void sendToDesktop() { - /** - * Before send message to home, we should make sure that current - * editing note is exists in databases. So, for new note, firstly - * save it - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - - if (mWorkingNote.getNoteId() > 0) { - Intent sender = new Intent(); - Intent shortcutIntent = new Intent(this, NoteEditActivity.class); - shortcutIntent.setAction(Intent.ACTION_VIEW); - shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, - makeShortcutIconTitle(mWorkingNote.getContent())); - sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); - sender.putExtra("duplicate", true); - sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - showToast(R.string.info_note_enter_desktop); - sendBroadcast(sender); - } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ - Log.e(TAG, "Send to desktop error"); - showToast(R.string.error_note_empty_for_send_to_desktop); - } - } - - private String makeShortcutIconTitle(String content) { - content = content.replace(TAG_CHECKED, ""); - content = content.replace(TAG_UNCHECKED, ""); - return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, - SHORTCUT_ICON_TITLE_MAX_LEN) : content; - } - - private void showToast(int resId) { - showToast(resId, Toast.LENGTH_SHORT); - } - - private void showToast(int resId, int duration) { - Toast.makeText(this, resId, duration).show(); - } -} diff --git a/src/net/micode/notes/ui/NoteEditText.java b/src/net/micode/notes/ui/NoteEditText.java deleted file mode 100644 index 2afe2a89..00000000 --- a/src/net/micode/notes/ui/NoteEditText.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.content.Context; -import android.graphics.Rect; -import android.text.Layout; -import android.text.Selection; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.URLSpan; -import android.util.AttributeSet; -import android.util.Log; -import android.view.ContextMenu; -import android.view.KeyEvent; -import android.view.MenuItem; -import android.view.MenuItem.OnMenuItemClickListener; -import android.view.MotionEvent; -import android.widget.EditText; - -import net.micode.notes.R; - -import java.util.HashMap; -import java.util.Map; - -public class NoteEditText extends EditText { - private static final String TAG = "NoteEditText"; - private int mIndex; - private int mSelectionStartBeforeDelete; - - private static final String SCHEME_TEL = "tel:" ; - private static final String SCHEME_HTTP = "http:" ; - private static final String SCHEME_EMAIL = "mailto:" ; - - private static final Map sSchemaActionResMap = new HashMap(); - static { - sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); - sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); - sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); - } - - /** - * Call by the {@link NoteEditActivity} to delete or add edit text - */ - public interface OnTextViewChangeListener { - /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null - */ - void onEditTextDelete(int index, String text); - - /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen - */ - void onEditTextEnter(int index, String text); - - /** - * Hide or show item option when text change - */ - void onTextChange(int index, boolean hasText); - } - - private OnTextViewChangeListener mOnTextViewChangeListener; - - public NoteEditText(Context context) { - super(context, null); - mIndex = 0; - } - - public void setIndex(int index) { - mIndex = index; - } - - public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { - mOnTextViewChangeListener = listener; - } - - public NoteEditText(Context context, AttributeSet attrs) { - super(context, attrs, android.R.attr.editTextStyle); - } - - public NoteEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - // TODO Auto-generated constructor stub - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - - int x = (int) event.getX(); - int y = (int) event.getY(); - x -= getTotalPaddingLeft(); - y -= getTotalPaddingTop(); - x += getScrollX(); - y += getScrollY(); - - Layout layout = getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - Selection.setSelection(getText(), off); - break; - } - - return super.onTouchEvent(event); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_ENTER: - if (mOnTextViewChangeListener != null) { - return false; - } - break; - case KeyEvent.KEYCODE_DEL: - mSelectionStartBeforeDelete = getSelectionStart(); - break; - default: - break; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - switch(keyCode) { - case KeyEvent.KEYCODE_DEL: - if (mOnTextViewChangeListener != null) { - if (0 == mSelectionStartBeforeDelete && mIndex != 0) { - mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); - return true; - } - } else { - Log.d(TAG, "OnTextViewChangeListener was not seted"); - } - break; - case KeyEvent.KEYCODE_ENTER: - if (mOnTextViewChangeListener != null) { - int selectionStart = getSelectionStart(); - String text = getText().subSequence(selectionStart, length()).toString(); - setText(getText().subSequence(0, selectionStart)); - mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); - } else { - Log.d(TAG, "OnTextViewChangeListener was not seted"); - } - break; - default: - break; - } - return super.onKeyUp(keyCode, event); - } - - @Override - protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { - if (mOnTextViewChangeListener != null) { - if (!focused && TextUtils.isEmpty(getText())) { - mOnTextViewChangeListener.onTextChange(mIndex, false); - } else { - mOnTextViewChangeListener.onTextChange(mIndex, true); - } - } - super.onFocusChanged(focused, direction, previouslyFocusedRect); - } - - @Override - protected void onCreateContextMenu(ContextMenu menu) { - if (getText() instanceof Spanned) { - int selStart = getSelectionStart(); - int selEnd = getSelectionEnd(); - - int min = Math.min(selStart, selEnd); - int max = Math.max(selStart, selEnd); - - final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); - if (urls.length == 1) { - int defaultResId = 0; - for(String schema: sSchemaActionResMap.keySet()) { - if(urls[0].getURL().indexOf(schema) >= 0) { - defaultResId = sSchemaActionResMap.get(schema); - break; - } - } - - if (defaultResId == 0) { - defaultResId = R.string.note_link_other; - } - - menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( - new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - // goto a new intent - urls[0].onClick(NoteEditText.this); - return true; - } - }); - } - } - super.onCreateContextMenu(menu); - } -} diff --git a/src/net/micode/notes/ui/NoteItemData.java b/src/net/micode/notes/ui/NoteItemData.java deleted file mode 100644 index 0f5a8782..00000000 --- a/src/net/micode/notes/ui/NoteItemData.java +++ /dev/null @@ -1,224 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.content.Context; -import android.database.Cursor; -import android.text.TextUtils; - -import net.micode.notes.data.Contact; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.tool.DataUtils; - - -public class NoteItemData { - static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE, - NoteColumns.BG_COLOR_ID, - NoteColumns.CREATED_DATE, - NoteColumns.HAS_ATTACHMENT, - NoteColumns.MODIFIED_DATE, - NoteColumns.NOTES_COUNT, - NoteColumns.PARENT_ID, - NoteColumns.SNIPPET, - NoteColumns.TYPE, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, - }; - - private static final int ID_COLUMN = 0; - private static final int ALERTED_DATE_COLUMN = 1; - private static final int BG_COLOR_ID_COLUMN = 2; - private static final int CREATED_DATE_COLUMN = 3; - private static final int HAS_ATTACHMENT_COLUMN = 4; - private static final int MODIFIED_DATE_COLUMN = 5; - private static final int NOTES_COUNT_COLUMN = 6; - private static final int PARENT_ID_COLUMN = 7; - private static final int SNIPPET_COLUMN = 8; - private static final int TYPE_COLUMN = 9; - private static final int WIDGET_ID_COLUMN = 10; - private static final int WIDGET_TYPE_COLUMN = 11; - - private long mId; - private long mAlertDate; - private int mBgColorId; - private long mCreatedDate; - private boolean mHasAttachment; - private long mModifiedDate; - private int mNotesCount; - private long mParentId; - private String mSnippet; - private int mType; - private int mWidgetId; - private int mWidgetType; - private String mName; - private String mPhoneNumber; - - private boolean mIsLastItem; - private boolean mIsFirstItem; - private boolean mIsOnlyOneItem; - private boolean mIsOneNoteFollowingFolder; - private boolean mIsMultiNotesFollowingFolder; - - public NoteItemData(Context context, Cursor cursor) { - mId = cursor.getLong(ID_COLUMN); - mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); - mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); - mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); - mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; - mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); - mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); - mParentId = cursor.getLong(PARENT_ID_COLUMN); - mSnippet = cursor.getString(SNIPPET_COLUMN); - mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( - NoteEditActivity.TAG_UNCHECKED, ""); - mType = cursor.getInt(TYPE_COLUMN); - mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); - mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); - - mPhoneNumber = ""; - if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { - mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); - if (!TextUtils.isEmpty(mPhoneNumber)) { - mName = Contact.getContact(context, mPhoneNumber); - if (mName == null) { - mName = mPhoneNumber; - } - } - } - - if (mName == null) { - mName = ""; - } - checkPostion(cursor); - } - - private void checkPostion(Cursor cursor) { - mIsLastItem = cursor.isLast() ? true : false; - mIsFirstItem = cursor.isFirst() ? true : false; - mIsOnlyOneItem = (cursor.getCount() == 1); - mIsMultiNotesFollowingFolder = false; - mIsOneNoteFollowingFolder = false; - - if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { - int position = cursor.getPosition(); - if (cursor.moveToPrevious()) { - if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER - || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { - if (cursor.getCount() > (position + 1)) { - mIsMultiNotesFollowingFolder = true; - } else { - mIsOneNoteFollowingFolder = true; - } - } - if (!cursor.moveToNext()) { - throw new IllegalStateException("cursor move to previous but can't move back"); - } - } - } - } - - public boolean isOneFollowingFolder() { - return mIsOneNoteFollowingFolder; - } - - public boolean isMultiFollowingFolder() { - return mIsMultiNotesFollowingFolder; - } - - public boolean isLast() { - return mIsLastItem; - } - - public String getCallName() { - return mName; - } - - public boolean isFirst() { - return mIsFirstItem; - } - - public boolean isSingle() { - return mIsOnlyOneItem; - } - - public long getId() { - return mId; - } - - public long getAlertDate() { - return mAlertDate; - } - - public long getCreatedDate() { - return mCreatedDate; - } - - public boolean hasAttachment() { - return mHasAttachment; - } - - public long getModifiedDate() { - return mModifiedDate; - } - - public int getBgColorId() { - return mBgColorId; - } - - public long getParentId() { - return mParentId; - } - - public int getNotesCount() { - return mNotesCount; - } - - public long getFolderId () { - return mParentId; - } - - public int getType() { - return mType; - } - - public int getWidgetType() { - return mWidgetType; - } - - public int getWidgetId() { - return mWidgetId; - } - - public String getSnippet() { - return mSnippet; - } - - public boolean hasAlert() { - return (mAlertDate > 0); - } - - public boolean isCallRecord() { - return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); - } - - public static int getNoteType(Cursor cursor) { - return cursor.getInt(TYPE_COLUMN); - } -} diff --git a/src/net/micode/notes/ui/NotesListActivity.java b/src/net/micode/notes/ui/NotesListActivity.java deleted file mode 100644 index e843aecf..00000000 --- a/src/net/micode/notes/ui/NotesListActivity.java +++ /dev/null @@ -1,954 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Dialog; -import android.appwidget.AppWidgetManager; -import android.content.AsyncQueryHandler; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.os.AsyncTask; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.Editable; -import android.text.TextUtils; -import android.text.TextWatcher; -import android.util.Log; -import android.view.ActionMode; -import android.view.ContextMenu; -import android.view.ContextMenu.ContextMenuInfo; -import android.view.Display; -import android.view.HapticFeedbackConstants; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MenuItem.OnMenuItemClickListener; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.View.OnCreateContextMenuListener; -import android.view.View.OnTouchListener; -import android.view.inputmethod.InputMethodManager; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; -import android.widget.AdapterView.OnItemLongClickListener; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.PopupMenu; -import android.widget.TextView; -import android.widget.Toast; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.gtask.remote.GTaskSyncService; -import net.micode.notes.model.WorkingNote; -import net.micode.notes.tool.BackupUtils; -import net.micode.notes.tool.DataUtils; -import net.micode.notes.tool.ResourceParser; -import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; -import net.micode.notes.widget.NoteWidgetProvider_2x; -import net.micode.notes.widget.NoteWidgetProvider_4x; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.HashSet; - -public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { - private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; - - private static final int FOLDER_LIST_QUERY_TOKEN = 1; - - private static final int MENU_FOLDER_DELETE = 0; - - private static final int MENU_FOLDER_VIEW = 1; - - private static final int MENU_FOLDER_CHANGE_NAME = 2; - - private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; - - private enum ListEditState { - NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER - }; - - private ListEditState mState; - - private BackgroundQueryHandler mBackgroundQueryHandler; - - private NotesListAdapter mNotesListAdapter; - - private ListView mNotesListView; - - private Button mAddNewNote; - - private boolean mDispatch; - - private int mOriginY; - - private int mDispatchY; - - private TextView mTitleBar; - - private long mCurrentFolderId; - - private ContentResolver mContentResolver; - - private ModeCallback mModeCallBack; - - private static final String TAG = "NotesListActivity"; - - public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; - - private NoteItemData mFocusNoteDataItem; - - private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; - - private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" - + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" - + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " - + NoteColumns.NOTES_COUNT + ">0)"; - - private final static int REQUEST_CODE_OPEN_NODE = 102; - private final static int REQUEST_CODE_NEW_NODE = 103; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.note_list); - initResources(); - - /** - * Insert an introduction when user firstly use this application - */ - setAppInfoFromRawRes(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == RESULT_OK - && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { - mNotesListAdapter.changeCursor(null); - } else { - super.onActivityResult(requestCode, resultCode, data); - } - } - - private void setAppInfoFromRawRes() { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); - if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { - StringBuilder sb = new StringBuilder(); - InputStream in = null; - try { - in = getResources().openRawResource(R.raw.introduction); - if (in != null) { - InputStreamReader isr = new InputStreamReader(in); - BufferedReader br = new BufferedReader(isr); - char [] buf = new char[1024]; - int len = 0; - while ((len = br.read(buf)) > 0) { - sb.append(buf, 0, len); - } - } else { - Log.e(TAG, "Read introduction file error"); - return; - } - } catch (IOException e) { - e.printStackTrace(); - return; - } finally { - if(in != null) { - try { - in.close(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - - WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, - AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, - ResourceParser.RED); - note.setWorkingText(sb.toString()); - if (note.saveNote()) { - sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); - } else { - Log.e(TAG, "Save introduction note error"); - return; - } - } - } - - @Override - protected void onStart() { - super.onStart(); - startAsyncNotesListQuery(); - } - - private void initResources() { - mContentResolver = this.getContentResolver(); - mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mNotesListView = (ListView) findViewById(R.id.notes_list); - mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), - null, false); - mNotesListView.setOnItemClickListener(new OnListItemClickListener()); - mNotesListView.setOnItemLongClickListener(this); - mNotesListAdapter = new NotesListAdapter(this); - mNotesListView.setAdapter(mNotesListAdapter); - mAddNewNote = (Button) findViewById(R.id.btn_new_note); - mAddNewNote.setOnClickListener(this); - mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); - mDispatch = false; - mDispatchY = 0; - mOriginY = 0; - mTitleBar = (TextView) findViewById(R.id.tv_title_bar); - mState = ListEditState.NOTE_LIST; - mModeCallBack = new ModeCallback(); - } - - private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { - private DropdownMenu mDropDownMenu; - private ActionMode mActionMode; - private MenuItem mMoveMenu; - - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - getMenuInflater().inflate(R.menu.note_list_options, menu); - menu.findItem(R.id.delete).setOnMenuItemClickListener(this); - mMoveMenu = menu.findItem(R.id.move); - if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER - || DataUtils.getUserFolderCount(mContentResolver) == 0) { - mMoveMenu.setVisible(false); - } else { - mMoveMenu.setVisible(true); - mMoveMenu.setOnMenuItemClickListener(this); - } - mActionMode = mode; - mNotesListAdapter.setChoiceMode(true); - mNotesListView.setLongClickable(false); - mAddNewNote.setVisibility(View.GONE); - - View customView = LayoutInflater.from(NotesListActivity.this).inflate( - R.layout.note_list_dropdown_menu, null); - mode.setCustomView(customView); - mDropDownMenu = new DropdownMenu(NotesListActivity.this, - (Button) customView.findViewById(R.id.selection_menu), - R.menu.note_list_dropdown); - mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ - public boolean onMenuItemClick(MenuItem item) { - mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); - updateMenu(); - return true; - } - - }); - return true; - } - - private void updateMenu() { - int selectedCount = mNotesListAdapter.getSelectedCount(); - // Update dropdown menu - String format = getResources().getString(R.string.menu_select_title, selectedCount); - mDropDownMenu.setTitle(format); - MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); - if (item != null) { - if (mNotesListAdapter.isAllSelected()) { - item.setChecked(true); - item.setTitle(R.string.menu_deselect_all); - } else { - item.setChecked(false); - item.setTitle(R.string.menu_select_all); - } - } - } - - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - // TODO Auto-generated method stub - return false; - } - - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // TODO Auto-generated method stub - return false; - } - - public void onDestroyActionMode(ActionMode mode) { - mNotesListAdapter.setChoiceMode(false); - mNotesListView.setLongClickable(true); - mAddNewNote.setVisibility(View.VISIBLE); - } - - public void finishActionMode() { - mActionMode.finish(); - } - - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - mNotesListAdapter.setCheckedItem(position, checked); - updateMenu(); - } - - public boolean onMenuItemClick(MenuItem item) { - if (mNotesListAdapter.getSelectedCount() == 0) { - Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), - Toast.LENGTH_SHORT).show(); - return true; - } - - switch (item.getItemId()) { - case R.id.delete: - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(getString(R.string.alert_title_delete)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setMessage(getString(R.string.alert_message_delete_notes, - mNotesListAdapter.getSelectedCount())); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int which) { - batchDelete(); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - break; - case R.id.move: - startQueryDestinationFolders(); - break; - default: - return false; - } - return true; - } - } - - private class NewNoteOnTouchListener implements OnTouchListener { - - public boolean onTouch(View v, MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: { - Display display = getWindowManager().getDefaultDisplay(); - int screenHeight = display.getHeight(); - int newNoteViewHeight = mAddNewNote.getHeight(); - int start = screenHeight - newNoteViewHeight; - int eventY = start + (int) event.getY(); - /** - * Minus TitleBar's height - */ - if (mState == ListEditState.SUB_FOLDER) { - eventY -= mTitleBar.getHeight(); - start -= mTitleBar.getHeight(); - } - /** - * HACKME:When click the transparent part of "New Note" button, dispatch - * the event to the list view behind this button. The transparent part of - * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) - * and the line top of the button. The coordinate based on left of the "New - * Note" button. The 94 represents maximum height of the transparent part. - * Notice that, if the background of the button changes, the formula should - * also change. This is very bad, just for the UI designer's strong requirement. - */ - if (event.getY() < (event.getX() * (-0.12) + 94)) { - View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 - - mNotesListView.getFooterViewsCount()); - if (view != null && view.getBottom() > start - && (view.getTop() < (start + 94))) { - mOriginY = (int) event.getY(); - mDispatchY = eventY; - event.setLocation(event.getX(), mDispatchY); - mDispatch = true; - return mNotesListView.dispatchTouchEvent(event); - } - } - break; - } - case MotionEvent.ACTION_MOVE: { - if (mDispatch) { - mDispatchY += (int) event.getY() - mOriginY; - event.setLocation(event.getX(), mDispatchY); - return mNotesListView.dispatchTouchEvent(event); - } - break; - } - default: { - if (mDispatch) { - event.setLocation(event.getX(), mDispatchY); - mDispatch = false; - return mNotesListView.dispatchTouchEvent(event); - } - break; - } - } - return false; - } - - }; - - private void startAsyncNotesListQuery() { - String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION - : NORMAL_SELECTION; - mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, - Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { - String.valueOf(mCurrentFolderId) - }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); - } - - private final class BackgroundQueryHandler extends AsyncQueryHandler { - public BackgroundQueryHandler(ContentResolver contentResolver) { - super(contentResolver); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - switch (token) { - case FOLDER_NOTE_LIST_QUERY_TOKEN: - mNotesListAdapter.changeCursor(cursor); - break; - case FOLDER_LIST_QUERY_TOKEN: - if (cursor != null && cursor.getCount() > 0) { - showFolderListMenu(cursor); - } else { - Log.e(TAG, "Query folder failed"); - } - break; - default: - return; - } - } - } - - private void showFolderListMenu(Cursor cursor) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(R.string.menu_title_select_folder); - final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); - builder.setAdapter(adapter, new DialogInterface.OnClickListener() { - - public void onClick(DialogInterface dialog, int which) { - DataUtils.batchMoveToFolder(mContentResolver, - mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); - Toast.makeText( - NotesListActivity.this, - getString(R.string.format_move_notes_to_folder, - mNotesListAdapter.getSelectedCount(), - adapter.getFolderName(NotesListActivity.this, which)), - Toast.LENGTH_SHORT).show(); - mModeCallBack.finishActionMode(); - } - }); - builder.show(); - } - - private void createNewNote() { - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); - this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); - } - - private void batchDelete() { - new AsyncTask>() { - protected HashSet doInBackground(Void... unused) { - HashSet widgets = mNotesListAdapter.getSelectedWidget(); - if (!isSyncMode()) { - // if not synced, delete notes directly - if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter - .getSelectedItemIds())) { - } else { - Log.e(TAG, "Delete notes error, should not happens"); - } - } else { - // in sync mode, we'll move the deleted note into the trash - // folder - if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter - .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { - Log.e(TAG, "Move notes to trash folder error, should not happens"); - } - } - return widgets; - } - - @Override - protected void onPostExecute(HashSet widgets) { - if (widgets != null) { - for (AppWidgetAttribute widget : widgets) { - if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { - updateWidget(widget.widgetId, widget.widgetType); - } - } - } - mModeCallBack.finishActionMode(); - } - }.execute(); - } - - private void deleteFolder(long folderId) { - if (folderId == Notes.ID_ROOT_FOLDER) { - Log.e(TAG, "Wrong folder id, should not happen " + folderId); - return; - } - - HashSet ids = new HashSet(); - ids.add(folderId); - HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, - folderId); - if (!isSyncMode()) { - // if not synced, delete folder directly - DataUtils.batchDeleteNotes(mContentResolver, ids); - } else { - // in sync mode, we'll move the deleted folder into the trash folder - DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); - } - if (widgets != null) { - for (AppWidgetAttribute widget : widgets) { - if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { - updateWidget(widget.widgetId, widget.widgetType); - } - } - } - } - - private void openNode(NoteItemData data) { - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, data.getId()); - this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); - } - - private void openFolder(NoteItemData data) { - mCurrentFolderId = data.getId(); - startAsyncNotesListQuery(); - if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mState = ListEditState.CALL_RECORD_FOLDER; - mAddNewNote.setVisibility(View.GONE); - } else { - mState = ListEditState.SUB_FOLDER; - } - if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mTitleBar.setText(R.string.call_record_folder_name); - } else { - mTitleBar.setText(data.getSnippet()); - } - mTitleBar.setVisibility(View.VISIBLE); - } - - public void onClick(View v) { - switch (v.getId()) { - case R.id.btn_new_note: - createNewNote(); - break; - default: - break; - } - } - - private void showSoftInput() { - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); - } - } - - private void hideSoftInput(View view) { - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); - } - - private void showCreateOrModifyFolderDialog(final boolean create) { - final AlertDialog.Builder builder = new AlertDialog.Builder(this); - View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); - final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); - showSoftInput(); - if (!create) { - if (mFocusNoteDataItem != null) { - etName.setText(mFocusNoteDataItem.getSnippet()); - builder.setTitle(getString(R.string.menu_folder_change_name)); - } else { - Log.e(TAG, "The long click data item is null"); - return; - } - } else { - etName.setText(""); - builder.setTitle(this.getString(R.string.menu_create_folder)); - } - - builder.setPositiveButton(android.R.string.ok, null); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - hideSoftInput(etName); - } - }); - - final Dialog dialog = builder.setView(view).show(); - final Button positive = (Button)dialog.findViewById(android.R.id.button1); - positive.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - hideSoftInput(etName); - String name = etName.getText().toString(); - if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { - Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), - Toast.LENGTH_LONG).show(); - etName.setSelection(0, etName.length()); - return; - } - if (!create) { - if (!TextUtils.isEmpty(name)) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - values.put(NoteColumns.LOCAL_MODIFIED, 1); - mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID - + "=?", new String[] { - String.valueOf(mFocusNoteDataItem.getId()) - }); - } - } else if (!TextUtils.isEmpty(name)) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); - } - dialog.dismiss(); - } - }); - - if (TextUtils.isEmpty(etName.getText())) { - positive.setEnabled(false); - } - /** - * When the name edit text is null, disable the positive button - */ - etName.addTextChangedListener(new TextWatcher() { - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // TODO Auto-generated method stub - - } - - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (TextUtils.isEmpty(etName.getText())) { - positive.setEnabled(false); - } else { - positive.setEnabled(true); - } - } - - public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - - } - }); - } - - @Override - public void onBackPressed() { - switch (mState) { - case SUB_FOLDER: - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mState = ListEditState.NOTE_LIST; - startAsyncNotesListQuery(); - mTitleBar.setVisibility(View.GONE); - break; - case CALL_RECORD_FOLDER: - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mState = ListEditState.NOTE_LIST; - mAddNewNote.setVisibility(View.VISIBLE); - mTitleBar.setVisibility(View.GONE); - startAsyncNotesListQuery(); - break; - case NOTE_LIST: - super.onBackPressed(); - break; - default: - break; - } - } - - private void updateWidget(int appWidgetId, int appWidgetType) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - if (appWidgetType == Notes.TYPE_WIDGET_2X) { - intent.setClass(this, NoteWidgetProvider_2x.class); - } else if (appWidgetType == Notes.TYPE_WIDGET_4X) { - intent.setClass(this, NoteWidgetProvider_4x.class); - } else { - Log.e(TAG, "Unspported widget type"); - return; - } - - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - appWidgetId - }); - - sendBroadcast(intent); - setResult(RESULT_OK, intent); - } - - private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - if (mFocusNoteDataItem != null) { - menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); - menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); - menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); - menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); - } - } - }; - - @Override - public void onContextMenuClosed(Menu menu) { - if (mNotesListView != null) { - mNotesListView.setOnCreateContextMenuListener(null); - } - super.onContextMenuClosed(menu); - } - - @Override - public boolean onContextItemSelected(MenuItem item) { - if (mFocusNoteDataItem == null) { - Log.e(TAG, "The long click data item is null"); - return false; - } - switch (item.getItemId()) { - case MENU_FOLDER_VIEW: - openFolder(mFocusNoteDataItem); - break; - case MENU_FOLDER_DELETE: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.alert_title_delete)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setMessage(getString(R.string.alert_message_delete_folder)); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - deleteFolder(mFocusNoteDataItem.getId()); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - break; - case MENU_FOLDER_CHANGE_NAME: - showCreateOrModifyFolderDialog(false); - break; - default: - break; - } - - return true; - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.clear(); - if (mState == ListEditState.NOTE_LIST) { - getMenuInflater().inflate(R.menu.note_list, menu); - // set sync or sync_cancel - menu.findItem(R.id.menu_sync).setTitle( - GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); - } else if (mState == ListEditState.SUB_FOLDER) { - getMenuInflater().inflate(R.menu.sub_folder, menu); - } else if (mState == ListEditState.CALL_RECORD_FOLDER) { - getMenuInflater().inflate(R.menu.call_record_folder, menu); - } else { - Log.e(TAG, "Wrong state:" + mState); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_new_folder: { - showCreateOrModifyFolderDialog(true); - break; - } - case R.id.menu_export_text: { - exportNoteToText(); - break; - } - case R.id.menu_sync: { - if (isSyncMode()) { - if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { - GTaskSyncService.startSync(this); - } else { - GTaskSyncService.cancelSync(this); - } - } else { - startPreferenceActivity(); - } - break; - } - case R.id.menu_setting: { - startPreferenceActivity(); - break; - } - case R.id.menu_new_note: { - createNewNote(); - break; - } - case R.id.menu_search: - onSearchRequested(); - break; - default: - break; - } - return true; - } - - @Override - public boolean onSearchRequested() { - startSearch(null, false, null /* appData */, false); - return true; - } - - private void exportNoteToText() { - final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); - new AsyncTask() { - - @Override - protected Integer doInBackground(Void... unused) { - return backup.exportToText(); - } - - @Override - protected void onPostExecute(Integer result) { - if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.failed_sdcard_export)); - builder.setMessage(NotesListActivity.this - .getString(R.string.error_sdcard_unmounted)); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); - } else if (result == BackupUtils.STATE_SUCCESS) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.success_sdcard_export)); - builder.setMessage(NotesListActivity.this.getString( - R.string.format_exported_file_location, backup - .getExportedTextFileName(), backup.getExportedTextFileDir())); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); - } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.failed_sdcard_export)); - builder.setMessage(NotesListActivity.this - .getString(R.string.error_sdcard_export)); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); - } - } - - }.execute(); - } - - private boolean isSyncMode() { - return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; - } - - private void startPreferenceActivity() { - Activity from = getParent() != null ? getParent() : this; - Intent intent = new Intent(from, NotesPreferenceActivity.class); - from.startActivityIfNeeded(intent, -1); - } - - private class OnListItemClickListener implements OnItemClickListener { - - public void onItemClick(AdapterView parent, View view, int position, long id) { - if (view instanceof NotesListItem) { - NoteItemData item = ((NotesListItem) view).getItemData(); - if (mNotesListAdapter.isInChoiceMode()) { - if (item.getType() == Notes.TYPE_NOTE) { - position = position - mNotesListView.getHeaderViewsCount(); - mModeCallBack.onItemCheckedStateChanged(null, position, id, - !mNotesListAdapter.isSelectedItem(position)); - } - return; - } - - switch (mState) { - case NOTE_LIST: - if (item.getType() == Notes.TYPE_FOLDER - || item.getType() == Notes.TYPE_SYSTEM) { - openFolder(item); - } else if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); - } else { - Log.e(TAG, "Wrong note type in NOTE_LIST"); - } - break; - case SUB_FOLDER: - case CALL_RECORD_FOLDER: - if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); - } else { - Log.e(TAG, "Wrong note type in SUB_FOLDER"); - } - break; - default: - break; - } - } - } - - } - - private void startQueryDestinationFolders() { - String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; - selection = (mState == ListEditState.NOTE_LIST) ? selection: - "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; - - mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, - null, - Notes.CONTENT_NOTE_URI, - FoldersListAdapter.PROJECTION, - selection, - new String[] { - String.valueOf(Notes.TYPE_FOLDER), - String.valueOf(Notes.ID_TRASH_FOLER), - String.valueOf(mCurrentFolderId) - }, - NoteColumns.MODIFIED_DATE + " DESC"); - } - - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - if (view instanceof NotesListItem) { - mFocusNoteDataItem = ((NotesListItem) view).getItemData(); - if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { - if (mNotesListView.startActionMode(mModeCallBack) != null) { - mModeCallBack.onItemCheckedStateChanged(null, position, id, true); - mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } else { - Log.e(TAG, "startActionMode fails"); - } - } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { - mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); - } - } - return false; - } -} diff --git a/src/net/micode/notes/ui/NotesListAdapter.java b/src/net/micode/notes/ui/NotesListAdapter.java deleted file mode 100644 index 51c9cb98..00000000 --- a/src/net/micode/notes/ui/NotesListAdapter.java +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.content.Context; -import android.database.Cursor; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; - -import net.micode.notes.data.Notes; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; - - -public class NotesListAdapter extends CursorAdapter { - private static final String TAG = "NotesListAdapter"; - private Context mContext; - private HashMap mSelectedIndex; - private int mNotesCount; - private boolean mChoiceMode; - - public static class AppWidgetAttribute { - public int widgetId; - public int widgetType; - }; - - public NotesListAdapter(Context context) { - super(context, null); - mSelectedIndex = new HashMap(); - mContext = context; - mNotesCount = 0; - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return new NotesListItem(context); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - if (view instanceof NotesListItem) { - NoteItemData itemData = new NoteItemData(context, cursor); - ((NotesListItem) view).bind(context, itemData, mChoiceMode, - isSelectedItem(cursor.getPosition())); - } - } - - public void setCheckedItem(final int position, final boolean checked) { - mSelectedIndex.put(position, checked); - notifyDataSetChanged(); - } - - public boolean isInChoiceMode() { - return mChoiceMode; - } - - public void setChoiceMode(boolean mode) { - mSelectedIndex.clear(); - mChoiceMode = mode; - } - - public void selectAll(boolean checked) { - Cursor cursor = getCursor(); - for (int i = 0; i < getCount(); i++) { - if (cursor.moveToPosition(i)) { - if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { - setCheckedItem(i, checked); - } - } - } - } - - public HashSet getSelectedItemIds() { - HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { - Long id = getItemId(position); - if (id == Notes.ID_ROOT_FOLDER) { - Log.d(TAG, "Wrong item id, should not happen"); - } else { - itemSet.add(id); - } - } - } - - return itemSet; - } - - public HashSet getSelectedWidget() { - HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { - Cursor c = (Cursor) getItem(position); - if (c != null) { - AppWidgetAttribute widget = new AppWidgetAttribute(); - NoteItemData item = new NoteItemData(mContext, c); - widget.widgetId = item.getWidgetId(); - widget.widgetType = item.getWidgetType(); - itemSet.add(widget); - /** - * Don't close cursor here, only the adapter could close it - */ - } else { - Log.e(TAG, "Invalid cursor"); - return null; - } - } - } - return itemSet; - } - - public int getSelectedCount() { - Collection values = mSelectedIndex.values(); - if (null == values) { - return 0; - } - Iterator iter = values.iterator(); - int count = 0; - while (iter.hasNext()) { - if (true == iter.next()) { - count++; - } - } - return count; - } - - public boolean isAllSelected() { - int checkedCount = getSelectedCount(); - return (checkedCount != 0 && checkedCount == mNotesCount); - } - - public boolean isSelectedItem(final int position) { - if (null == mSelectedIndex.get(position)) { - return false; - } - return mSelectedIndex.get(position); - } - - @Override - protected void onContentChanged() { - super.onContentChanged(); - calcNotesCount(); - } - - @Override - public void changeCursor(Cursor cursor) { - super.changeCursor(cursor); - calcNotesCount(); - } - - private void calcNotesCount() { - mNotesCount = 0; - for (int i = 0; i < getCount(); i++) { - Cursor c = (Cursor) getItem(i); - if (c != null) { - if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { - mNotesCount++; - } - } else { - Log.e(TAG, "Invalid cursor"); - return; - } - } - } -} diff --git a/src/net/micode/notes/ui/NotesListItem.java b/src/net/micode/notes/ui/NotesListItem.java deleted file mode 100644 index 1221e808..00000000 --- a/src/net/micode/notes/ui/NotesListItem.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.content.Context; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.DataUtils; -import net.micode.notes.tool.ResourceParser.NoteItemBgResources; - - -public class NotesListItem extends LinearLayout { - private ImageView mAlert; - private TextView mTitle; - private TextView mTime; - private TextView mCallName; - private NoteItemData mItemData; - private CheckBox mCheckBox; - - public NotesListItem(Context context) { - super(context); - inflate(context, R.layout.note_item, this); - mAlert = (ImageView) findViewById(R.id.iv_alert_icon); - mTitle = (TextView) findViewById(R.id.tv_title); - mTime = (TextView) findViewById(R.id.tv_time); - mCallName = (TextView) findViewById(R.id.tv_name); - mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); - } - - public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { - if (choiceMode && data.getType() == Notes.TYPE_NOTE) { - mCheckBox.setVisibility(View.VISIBLE); - mCheckBox.setChecked(checked); - } else { - mCheckBox.setVisibility(View.GONE); - } - - mItemData = data; - if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.GONE); - mAlert.setVisibility(View.VISIBLE); - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); - mTitle.setText(context.getString(R.string.call_record_folder_name) - + context.getString(R.string.format_folder_files_count, data.getNotesCount())); - mAlert.setImageResource(R.drawable.call_record); - } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.VISIBLE); - mCallName.setText(data.getCallName()); - mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); - if (data.hasAlert()) { - mAlert.setImageResource(R.drawable.clock); - mAlert.setVisibility(View.VISIBLE); - } else { - mAlert.setVisibility(View.GONE); - } - } else { - mCallName.setVisibility(View.GONE); - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); - - if (data.getType() == Notes.TYPE_FOLDER) { - mTitle.setText(data.getSnippet() - + context.getString(R.string.format_folder_files_count, - data.getNotesCount())); - mAlert.setVisibility(View.GONE); - } else { - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); - if (data.hasAlert()) { - mAlert.setImageResource(R.drawable.clock); - mAlert.setVisibility(View.VISIBLE); - } else { - mAlert.setVisibility(View.GONE); - } - } - } - mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); - - setBackground(data); - } - - private void setBackground(NoteItemData data) { - int id = data.getBgColorId(); - if (data.getType() == Notes.TYPE_NOTE) { - if (data.isSingle() || data.isOneFollowingFolder()) { - setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); - } else if (data.isLast()) { - setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); - } else if (data.isFirst() || data.isMultiFollowingFolder()) { - setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); - } else { - setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); - } - } else { - setBackgroundResource(NoteItemBgResources.getFolderBgRes()); - } - } - - public NoteItemData getItemData() { - return mItemData; - } -} diff --git a/src/net/micode/notes/ui/NotesPreferenceActivity.java b/src/net/micode/notes/ui/NotesPreferenceActivity.java deleted file mode 100644 index 07c5f7ec..00000000 --- a/src/net/micode/notes/ui/NotesPreferenceActivity.java +++ /dev/null @@ -1,388 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.ui; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.app.ActionBar; -import android.app.AlertDialog; -import android.content.BroadcastReceiver; -import android.content.ContentValues; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.Preference; -import android.preference.Preference.OnPreferenceClickListener; -import android.preference.PreferenceActivity; -import android.preference.PreferenceCategory; -import android.text.TextUtils; -import android.text.format.DateFormat; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.gtask.remote.GTaskSyncService; - - -public class NotesPreferenceActivity extends PreferenceActivity { - public static final String PREFERENCE_NAME = "notes_preferences"; - - public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; - - public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; - - public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; - - private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; - - private static final String AUTHORITIES_FILTER_KEY = "authorities"; - - private PreferenceCategory mAccountCategory; - - private GTaskReceiver mReceiver; - - private Account[] mOriAccounts; - - private boolean mHasAddedAccount; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - /* using the app icon for navigation */ - getActionBar().setDisplayHomeAsUpEnabled(true); - - addPreferencesFromResource(R.xml.preferences); - mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); - mReceiver = new GTaskReceiver(); - IntentFilter filter = new IntentFilter(); - filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); - registerReceiver(mReceiver, filter); - - mOriAccounts = null; - View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); - getListView().addHeaderView(header, null, true); - } - - @Override - protected void onResume() { - super.onResume(); - - // need to set sync account automatically if user has added a new - // account - if (mHasAddedAccount) { - Account[] accounts = getGoogleAccounts(); - if (mOriAccounts != null && accounts.length > mOriAccounts.length) { - for (Account accountNew : accounts) { - boolean found = false; - for (Account accountOld : mOriAccounts) { - if (TextUtils.equals(accountOld.name, accountNew.name)) { - found = true; - break; - } - } - if (!found) { - setSyncAccount(accountNew.name); - break; - } - } - } - } - - refreshUI(); - } - - @Override - protected void onDestroy() { - if (mReceiver != null) { - unregisterReceiver(mReceiver); - } - super.onDestroy(); - } - - private void loadAccountPreference() { - mAccountCategory.removeAll(); - - Preference accountPref = new Preference(this); - final String defaultAccount = getSyncAccountName(this); - accountPref.setTitle(getString(R.string.preferences_account_title)); - accountPref.setSummary(getString(R.string.preferences_account_summary)); - accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { - public boolean onPreferenceClick(Preference preference) { - if (!GTaskSyncService.isSyncing()) { - if (TextUtils.isEmpty(defaultAccount)) { - // the first time to set account - showSelectAccountAlertDialog(); - } else { - // if the account has already been set, we need to promp - // user about the risk - showChangeAccountConfirmAlertDialog(); - } - } else { - Toast.makeText(NotesPreferenceActivity.this, - R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) - .show(); - } - return true; - } - }); - - mAccountCategory.addPreference(accountPref); - } - - private void loadSyncButton() { - Button syncButton = (Button) findViewById(R.id.preference_sync_button); - TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); - - // set button state - if (GTaskSyncService.isSyncing()) { - syncButton.setText(getString(R.string.preferences_button_sync_cancel)); - syncButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - GTaskSyncService.cancelSync(NotesPreferenceActivity.this); - } - }); - } else { - syncButton.setText(getString(R.string.preferences_button_sync_immediately)); - syncButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - GTaskSyncService.startSync(NotesPreferenceActivity.this); - } - }); - } - syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); - - // set last sync time - if (GTaskSyncService.isSyncing()) { - lastSyncTimeView.setText(GTaskSyncService.getProgressString()); - lastSyncTimeView.setVisibility(View.VISIBLE); - } else { - long lastSyncTime = getLastSyncTime(this); - if (lastSyncTime != 0) { - lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, - DateFormat.format(getString(R.string.preferences_last_sync_time_format), - lastSyncTime))); - lastSyncTimeView.setVisibility(View.VISIBLE); - } else { - lastSyncTimeView.setVisibility(View.GONE); - } - } - } - - private void refreshUI() { - loadAccountPreference(); - loadSyncButton(); - } - - private void showSelectAccountAlertDialog() { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - - View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); - TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); - titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); - TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); - subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); - - dialogBuilder.setCustomTitle(titleView); - dialogBuilder.setPositiveButton(null, null); - - Account[] accounts = getGoogleAccounts(); - String defAccount = getSyncAccountName(this); - - mOriAccounts = accounts; - mHasAddedAccount = false; - - if (accounts.length > 0) { - CharSequence[] items = new CharSequence[accounts.length]; - final CharSequence[] itemMapping = items; - int checkedItem = -1; - int index = 0; - for (Account account : accounts) { - if (TextUtils.equals(account.name, defAccount)) { - checkedItem = index; - } - items[index++] = account.name; - } - dialogBuilder.setSingleChoiceItems(items, checkedItem, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - setSyncAccount(itemMapping[which].toString()); - dialog.dismiss(); - refreshUI(); - } - }); - } - - View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); - dialogBuilder.setView(addAccountView); - - final AlertDialog dialog = dialogBuilder.show(); - addAccountView.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - mHasAddedAccount = true; - Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); - intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { - "gmail-ls" - }); - startActivityForResult(intent, -1); - dialog.dismiss(); - } - }); - } - - private void showChangeAccountConfirmAlertDialog() { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); - - View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); - TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); - titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, - getSyncAccountName(this))); - TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); - subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); - dialogBuilder.setCustomTitle(titleView); - - CharSequence[] menuItemArray = new CharSequence[] { - getString(R.string.preferences_menu_change_account), - getString(R.string.preferences_menu_remove_account), - getString(R.string.preferences_menu_cancel) - }; - dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - showSelectAccountAlertDialog(); - } else if (which == 1) { - removeSyncAccount(); - refreshUI(); - } - } - }); - dialogBuilder.show(); - } - - private Account[] getGoogleAccounts() { - AccountManager accountManager = AccountManager.get(this); - return accountManager.getAccountsByType("com.google"); - } - - private void setSyncAccount(String account) { - if (!getSyncAccountName(this).equals(account)) { - SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - if (account != null) { - editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); - } else { - editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); - } - editor.commit(); - - // clean up last sync time - setLastSyncTime(this, 0); - - // clean up local gtask related info - new Thread(new Runnable() { - public void run() { - ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); - getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); - } - }).start(); - - Toast.makeText(NotesPreferenceActivity.this, - getString(R.string.preferences_toast_success_set_accout, account), - Toast.LENGTH_SHORT).show(); - } - } - - private void removeSyncAccount() { - SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { - editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); - } - if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { - editor.remove(PREFERENCE_LAST_SYNC_TIME); - } - editor.commit(); - - // clean up local gtask related info - new Thread(new Runnable() { - public void run() { - ContentValues values = new ContentValues(); - values.put(NoteColumns.GTASK_ID, ""); - values.put(NoteColumns.SYNC_ID, 0); - getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); - } - }).start(); - } - - public static String getSyncAccountName(Context context) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); - } - - public static void setLastSyncTime(Context context, long time) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - SharedPreferences.Editor editor = settings.edit(); - editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); - editor.commit(); - } - - public static long getLastSyncTime(Context context) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); - } - - private class GTaskReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - refreshUI(); - if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { - TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); - syncStatus.setText(intent - .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); - } - - } - } - - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - Intent intent = new Intent(this, NotesListActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - default: - return false; - } - } -} diff --git a/src/net/micode/notes/widget/NoteWidgetProvider.java b/src/net/micode/notes/widget/NoteWidgetProvider.java deleted file mode 100644 index ec6f8193..00000000 --- a/src/net/micode/notes/widget/NoteWidgetProvider.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.widget; -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.util.Log; -import android.widget.RemoteViews; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.tool.ResourceParser; -import net.micode.notes.ui.NoteEditActivity; -import net.micode.notes.ui.NotesListActivity; - -public abstract class NoteWidgetProvider extends AppWidgetProvider { - public static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.BG_COLOR_ID, - NoteColumns.SNIPPET - }; - - public static final int COLUMN_ID = 0; - public static final int COLUMN_BG_COLOR_ID = 1; - public static final int COLUMN_SNIPPET = 2; - - private static final String TAG = "NoteWidgetProvider"; - - @Override - public void onDeleted(Context context, int[] appWidgetIds) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); - for (int i = 0; i < appWidgetIds.length; i++) { - context.getContentResolver().update(Notes.CONTENT_NOTE_URI, - values, - NoteColumns.WIDGET_ID + "=?", - new String[] { String.valueOf(appWidgetIds[i])}); - } - } - - private Cursor getNoteWidgetInfo(Context context, int widgetId) { - return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, - PROJECTION, - NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", - new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, - null); - } - - protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - update(context, appWidgetManager, appWidgetIds, false); - } - - private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, - boolean privacyMode) { - for (int i = 0; i < appWidgetIds.length; i++) { - if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { - int bgId = ResourceParser.getDefaultBgId(context); - String snippet = ""; - Intent intent = new Intent(context, NoteEditActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); - intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); - intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); - - Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); - if (c != null && c.moveToFirst()) { - if (c.getCount() > 1) { - Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); - c.close(); - return; - } - snippet = c.getString(COLUMN_SNIPPET); - bgId = c.getInt(COLUMN_BG_COLOR_ID); - intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); - intent.setAction(Intent.ACTION_VIEW); - } else { - snippet = context.getResources().getString(R.string.widget_havenot_content); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - } - - if (c != null) { - c.close(); - } - - RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); - rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); - intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); - /** - * Generate the pending intent to start host for the widget - */ - PendingIntent pendingIntent = null; - if (privacyMode) { - rv.setTextViewText(R.id.widget_text, - context.getString(R.string.widget_under_visit_mode)); - pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( - context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); - } else { - rv.setTextViewText(R.id.widget_text, snippet); - pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } - - rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); - appWidgetManager.updateAppWidget(appWidgetIds[i], rv); - } - } - } - - protected abstract int getBgResourceId(int bgId); - - protected abstract int getLayoutId(); - - protected abstract int getWidgetType(); -} diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_2x.java b/src/net/micode/notes/widget/NoteWidgetProvider_2x.java deleted file mode 100644 index adcb2f7b..00000000 --- a/src/net/micode/notes/widget/NoteWidgetProvider_2x.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.widget; - -import android.appwidget.AppWidgetManager; -import android.content.Context; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.ResourceParser; - - -public class NoteWidgetProvider_2x extends NoteWidgetProvider { - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - super.update(context, appWidgetManager, appWidgetIds); - } - - @Override - protected int getLayoutId() { - return R.layout.widget_2x; - } - - @Override - protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); - } - - @Override - protected int getWidgetType() { - return Notes.TYPE_WIDGET_2X; - } -} diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_4x.java b/src/net/micode/notes/widget/NoteWidgetProvider_4x.java deleted file mode 100644 index c12a02e4..00000000 --- a/src/net/micode/notes/widget/NoteWidgetProvider_4x.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package net.micode.notes.widget; - -import android.appwidget.AppWidgetManager; -import android.content.Context; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.ResourceParser; - - -public class NoteWidgetProvider_4x extends NoteWidgetProvider { - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - super.update(context, appWidgetManager, appWidgetIds); - } - - protected int getLayoutId() { - return R.layout.widget_4x; - } - - @Override - protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); - } - - @Override - protected int getWidgetType() { - return Notes.TYPE_WIDGET_4X; - } -}