diff --git a/README.md b/README.md index 158bf31..ccf5937 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ device and provides it to other elimu.ai apps.  -See software architecture diagram at https://github.com/elimu-ai/model/blob/master/README.md +See software architecture diagram at https://github.com/elimu-ai/model/blob/main/README.md ## Software Architecture [ <img width="320" alt="Software Architecture" src="https://user-images.githubusercontent.com/15718174/83595568-fb6a1e00-a594-11ea-990a-10c0bd62ed11.png"> -](https://github.com/elimu-ai/wiki/blob/master/SOFTWARE_ARCHITECTURE.md) +](https://github.com/elimu-ai/wiki/blob/main/SOFTWARE_ARCHITECTURE.md) ## Utils Library 📦 @@ -89,6 +89,38 @@ After that, connect your Android device to the same Wi-Fi network as your comput ./gradlew wrapper --gradle-version x.x.x --distribution-type all ``` +### Database Migration 🔀 + +When adding a new database `@Entity` (or modifying an existing one), you need to prepare a database +migration (SQL script) in +[`app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java`](app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java). + +Follow these steps: + +1. Add the new/modified `@Entity` to [`app/src/main/java/ai/elimu/content_provider/room/entity/`](app/src/main/java/ai/elimu/content_provider/room/entity/) +1. Add the entity's DAO interface to [`app/src/main/java/ai/elimu/content_provider/room/dao/`](app/src/main/java/ai/elimu/content_provider/room/dao/) +1. Include the DAO interface in [`app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java`](app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java) +1. Include the entity in the `entities` section of the `@Database` in [`app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java`](app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java) +1. Bump the `@Database` version in [`app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java`](app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java) +1. Build the code with `./gradlew clean build` +1. Open the new database schema generated at `app/schemas/ai.elimu.content_provider.room.db.RoomDb/<version>.json` +- Under `entities`, find the matching `tableName` and copy its SQL script from the `createSql` property. +1. Open `RoomDb.java` and add a new method for the latest migration +- Paste the SQL script from the above JSON schema, and replace `${TABLE_NAME}` with the name of the table you created/modified. +- Include the migration in the `getDatabase` method in `RoomDb.java`. +1. To run the database migration, launch the application on your device. + +**Tip #1:** To verify that your database migration ran successfully, look at the Logcat output and +ensure that there are no RoomDb errors: +``` +2023-11-27 11:46:50.662 6124-13233 ai.elimu.c....RoomDb$18 ai.elimu.content_provider.debug I migrate (23 --> 24) +2023-11-27 11:46:50.663 6124-13233 ai.elimu.c....RoomDb$18 ai.elimu.content_provider.debug I sql: CREATE TABLE IF NOT EXISTS `LetterSound` (`revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`)) +``` + +**Tip #2:** You can also use Android Studio's _Database Inspector_ to verify that the database +migration succeeded: + + ## Release 📦 diff --git a/app/schemas/ai.elimu.content_provider.room.db.RoomDb/24.json b/app/schemas/ai.elimu.content_provider.room.db.RoomDb/24.json new file mode 100644 index 0000000..42b5964 --- /dev/null +++ b/app/schemas/ai.elimu.content_provider.room.db.RoomDb/24.json @@ -0,0 +1,629 @@ +{ + "formatVersion": 1, + "database": { + "version": 24, + "identityHash": "272858d2cd99837310e007914ec3306b", + "entities": [ + { + "tableName": "Letter", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`text` TEXT, `diacritic` INTEGER, `revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "diacritic", + "columnName": "diacritic", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Sound", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`valueIpa` TEXT, `diacritic` INTEGER, `revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "valueIpa", + "columnName": "valueIpa", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "diacritic", + "columnName": "diacritic", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LetterSound", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Word", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`text` TEXT NOT NULL, `wordType` TEXT, `revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "wordType", + "columnName": "wordType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Number", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`value` INTEGER NOT NULL, `symbol` TEXT, `revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "value", + "columnName": "value", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "symbol", + "columnName": "symbol", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Emoji", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`glyph` TEXT NOT NULL, `unicodeVersion` REAL NOT NULL, `unicodeEmojiVersion` REAL NOT NULL, `revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "glyph", + "columnName": "glyph", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unicodeVersion", + "columnName": "unicodeVersion", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "unicodeEmojiVersion", + "columnName": "unicodeEmojiVersion", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Emoji_Word", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`Emoji_id` INTEGER NOT NULL, `words_id` INTEGER NOT NULL, PRIMARY KEY(`Emoji_id`, `words_id`))", + "fields": [ + { + "fieldPath": "Emoji_id", + "columnName": "Emoji_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "words_id", + "columnName": "words_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "Emoji_id", + "words_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Image", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `imageFormat` TEXT NOT NULL, `revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "imageFormat", + "columnName": "imageFormat", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Image_Word", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`Image_id` INTEGER NOT NULL, `words_id` INTEGER NOT NULL, PRIMARY KEY(`Image_id`, `words_id`))", + "fields": [ + { + "fieldPath": "Image_id", + "columnName": "Image_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "words_id", + "columnName": "words_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "Image_id", + "words_id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Audio", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `transcription` TEXT NOT NULL, `audioFormat` TEXT NOT NULL, `revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "transcription", + "columnName": "transcription", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "audioFormat", + "columnName": "audioFormat", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StoryBook", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `description` TEXT, `coverImageId` INTEGER NOT NULL, `readingLevel` TEXT, `revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverImageId", + "columnName": "coverImageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "readingLevel", + "columnName": "readingLevel", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StoryBookChapter", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`storyBookId` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL, `imageId` INTEGER NOT NULL, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "storyBookId", + "columnName": "storyBookId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sortOrder", + "columnName": "sortOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageId", + "columnName": "imageId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StoryBookParagraph", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`storyBookChapterId` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL, `originalText` TEXT NOT NULL, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "storyBookChapterId", + "columnName": "storyBookChapterId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sortOrder", + "columnName": "sortOrder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "originalText", + "columnName": "originalText", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "StoryBookParagraph_Word", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`StoryBookParagraph_id` INTEGER NOT NULL, `words_id` INTEGER NOT NULL, `words_ORDER` INTEGER NOT NULL, PRIMARY KEY(`StoryBookParagraph_id`, `words_ORDER`))", + "fields": [ + { + "fieldPath": "StoryBookParagraph_id", + "columnName": "StoryBookParagraph_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "words_id", + "columnName": "words_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "words_ORDER", + "columnName": "words_ORDER", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "StoryBookParagraph_id", + "words_ORDER" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Video", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`title` TEXT NOT NULL, `videoFormat` TEXT NOT NULL, `revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "videoFormat", + "columnName": "videoFormat", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "revisionNumber", + "columnName": "revisionNumber", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "usageCount", + "columnName": "usageCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '272858d2cd99837310e007914ec3306b')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/ai/elimu/content_provider/MainActivity.java b/app/src/main/java/ai/elimu/content_provider/MainActivity.java index c03b7d2..dc94065 100644 --- a/app/src/main/java/ai/elimu/content_provider/MainActivity.java +++ b/app/src/main/java/ai/elimu/content_provider/MainActivity.java @@ -53,6 +53,7 @@ protected void onCreate(Bundle savedInstanceState) { R.id.nav_home, R.id.nav_letters, R.id.nav_sounds, + R.id.nav_letter_sounds, R.id.nav_words, R.id.nav_numbers, R.id.nav_emojis, diff --git a/app/src/main/java/ai/elimu/content_provider/provider/LetterContentProvider.java b/app/src/main/java/ai/elimu/content_provider/provider/LetterContentProvider.java index d2c8130..4f1ccf2 100644 --- a/app/src/main/java/ai/elimu/content_provider/provider/LetterContentProvider.java +++ b/app/src/main/java/ai/elimu/content_provider/provider/LetterContentProvider.java @@ -14,6 +14,7 @@ import ai.elimu.content_provider.room.dao.LetterDao; import ai.elimu.content_provider.room.db.RoomDb; +@Deprecated public class LetterContentProvider extends ContentProvider { private static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider.letter_provider"; diff --git a/app/src/main/java/ai/elimu/content_provider/rest/LetterSoundsService.java b/app/src/main/java/ai/elimu/content_provider/rest/LetterSoundsService.java new file mode 100644 index 0000000..0dfd3f7 --- /dev/null +++ b/app/src/main/java/ai/elimu/content_provider/rest/LetterSoundsService.java @@ -0,0 +1,13 @@ +package ai.elimu.content_provider.rest; + +import java.util.List; + +import ai.elimu.model.v2.gson.content.LetterSoundGson; +import retrofit2.Call; +import retrofit2.http.GET; + +public interface LetterSoundsService { + + @GET("content/letter-sounds") + Call<List<LetterSoundGson>> listLetterSounds(); +} diff --git a/app/src/main/java/ai/elimu/content_provider/room/GsonToRoomConverter.java b/app/src/main/java/ai/elimu/content_provider/room/GsonToRoomConverter.java index 99bf374..2bf8edf 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/GsonToRoomConverter.java +++ b/app/src/main/java/ai/elimu/content_provider/room/GsonToRoomConverter.java @@ -1,5 +1,6 @@ package ai.elimu.content_provider.room; +import ai.elimu.content_provider.room.entity.LetterSound; import ai.elimu.content_provider.room.entity.Sound; import ai.elimu.content_provider.room.entity.Audio; import ai.elimu.content_provider.room.entity.Emoji; @@ -11,6 +12,7 @@ import ai.elimu.content_provider.room.entity.StoryBookParagraph; import ai.elimu.content_provider.room.entity.Video; import ai.elimu.content_provider.room.entity.Word; +import ai.elimu.model.v2.gson.content.LetterSoundGson; import ai.elimu.model.v2.gson.content.SoundGson; import ai.elimu.model.v2.gson.content.AudioGson; import ai.elimu.model.v2.gson.content.EmojiGson; @@ -67,6 +69,27 @@ public static Sound getSound(SoundGson soundGson) { } } + public static LetterSound getLetterSound(LetterSoundGson letterSoundGson) { + if (letterSoundGson == null) { + return null; + } else { + LetterSound letterSound = new LetterSound(); + + // BaseEntity + letterSound.setId(letterSoundGson.getId()); + + // Content + letterSound.setRevisionNumber(letterSoundGson.getRevisionNumber()); + letterSound.setUsageCount(letterSoundGson.getUsageCount()); + + // LetterSound + // TODO: letters + // TODO: sounds + + return letterSound; + } + } + public static Word getWord(WordGson wordGson) { if (wordGson == null) { return null; diff --git a/app/src/main/java/ai/elimu/content_provider/room/dao/LetterSoundDao.java b/app/src/main/java/ai/elimu/content_provider/room/dao/LetterSoundDao.java new file mode 100644 index 0000000..b37c1d0 --- /dev/null +++ b/app/src/main/java/ai/elimu/content_provider/room/dao/LetterSoundDao.java @@ -0,0 +1,22 @@ +package ai.elimu.content_provider.room.dao; + +import androidx.room.Dao; +import androidx.room.Insert; +import androidx.room.Query; + +import java.util.List; + +import ai.elimu.content_provider.room.entity.LetterSound; + +@Dao +public interface LetterSoundDao { + + @Insert + void insert(LetterSound letterSound); + + @Query("SELECT * FROM LetterSound") + List<LetterSound> loadAll(); + + @Query("DELETE FROM LetterSound") + void deleteAll(); +} diff --git a/app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java b/app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java index 1ae30bc..7562f12 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java +++ b/app/src/main/java/ai/elimu/content_provider/room/db/RoomDb.java @@ -13,6 +13,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import ai.elimu.content_provider.room.dao.LetterSoundDao; import ai.elimu.content_provider.room.dao.SoundDao; import ai.elimu.content_provider.room.dao.AudioDao; import ai.elimu.content_provider.room.dao.EmojiDao; @@ -27,6 +28,7 @@ import ai.elimu.content_provider.room.dao.StoryBookParagraph_WordDao; import ai.elimu.content_provider.room.dao.VideoDao; import ai.elimu.content_provider.room.dao.WordDao; +import ai.elimu.content_provider.room.entity.LetterSound; import ai.elimu.content_provider.room.entity.Sound; import ai.elimu.content_provider.room.entity.Audio; import ai.elimu.content_provider.room.entity.Emoji; @@ -42,7 +44,7 @@ import ai.elimu.content_provider.room.entity.Video; import ai.elimu.content_provider.room.entity.Word; -@Database(version = 23, entities = {Letter.class, Sound.class, Word.class, Number.class, Emoji.class, Emoji_Word.class, Image.class, Image_Word.class, Audio.class, StoryBook.class, StoryBookChapter.class, StoryBookParagraph.class, StoryBookParagraph_Word.class, Video.class}) +@Database(version = 24, entities = {Letter.class, Sound.class, LetterSound.class, Word.class, Number.class, Emoji.class, Emoji_Word.class, Image.class, Image_Word.class, Audio.class, StoryBook.class, StoryBookChapter.class, StoryBookParagraph.class, StoryBookParagraph_Word.class, Video.class}) @TypeConverters({Converters.class}) public abstract class RoomDb extends RoomDatabase { @@ -50,6 +52,8 @@ public abstract class RoomDb extends RoomDatabase { public abstract SoundDao soundDao(); + public abstract LetterSoundDao letterSoundDao(); + public abstract WordDao wordDao(); public abstract NumberDao numberDao(); @@ -105,7 +109,8 @@ public static RoomDb getDatabase(final Context context) { MIGRATION_19_20, MIGRATION_20_21, MIGRATION_21_22, - MIGRATION_22_23 + MIGRATION_22_23, + MIGRATION_23_24 ) .build(); } @@ -314,4 +319,15 @@ public void migrate(SupportSQLiteDatabase database) { database.execSQL(sql); } }; + + private static final Migration MIGRATION_23_24 = new Migration(23, 24) { + @Override + public void migrate(SupportSQLiteDatabase database) { + Log.i(getClass().getName(), "migrate (23 --> 24)"); + + String sql = "CREATE TABLE IF NOT EXISTS `LetterSound` (`revisionNumber` INTEGER NOT NULL, `usageCount` INTEGER, `id` INTEGER, PRIMARY KEY(`id`))"; + Log.i(getClass().getName(), "sql: " + sql); + database.execSQL(sql); + } + }; } diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Audio.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Audio.java index 9a85769..b914172 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Audio.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Audio.java @@ -6,7 +6,7 @@ import ai.elimu.model.v2.enums.content.AudioFormat; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class Audio extends Content { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/BaseEntity.java b/app/src/main/java/ai/elimu/content_provider/room/entity/BaseEntity.java index 1e3f364..7d4e97e 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/BaseEntity.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/BaseEntity.java @@ -3,7 +3,7 @@ import androidx.room.PrimaryKey; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ public class BaseEntity { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Content.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Content.java index cbe4993..c282a1d 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Content.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Content.java @@ -3,7 +3,7 @@ import androidx.annotation.NonNull; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ public class Content extends BaseEntity { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Emoji.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Emoji.java index 94facd2..df44c79 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Emoji.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Emoji.java @@ -4,7 +4,7 @@ import androidx.room.Entity; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class Emoji extends Content { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Emoji_Word.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Emoji_Word.java index ce9cbb7..9238618 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Emoji_Word.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Emoji_Word.java @@ -4,7 +4,7 @@ import androidx.room.Entity; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity(primaryKeys = {"Emoji_id", "words_id"}) public class Emoji_Word { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Image.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Image.java index 17a84b5..38a3f42 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Image.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Image.java @@ -6,7 +6,7 @@ import ai.elimu.model.v2.enums.content.ImageFormat; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class Image extends Content { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Image_Word.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Image_Word.java index 0025caf..40728e3 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Image_Word.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Image_Word.java @@ -4,7 +4,7 @@ import androidx.room.Entity; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity(primaryKeys = {"Image_id", "words_id"}) public class Image_Word { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Letter.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Letter.java index 7cb168b..3c507d1 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Letter.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Letter.java @@ -3,7 +3,7 @@ import androidx.room.Entity; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class Letter extends Content { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/LetterSound.java b/app/src/main/java/ai/elimu/content_provider/room/entity/LetterSound.java new file mode 100644 index 0000000..9d292a2 --- /dev/null +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/LetterSound.java @@ -0,0 +1,12 @@ +package ai.elimu.content_provider.room.entity; + +import androidx.room.Entity; + +/** + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model + */ +@Entity +public class LetterSound extends Content { + + +} diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Number.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Number.java index cbeec33..f47f6eb 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Number.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Number.java @@ -4,7 +4,7 @@ import androidx.room.Entity; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class Number extends Content { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Sound.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Sound.java index bd5bcd0..1b76836 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Sound.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Sound.java @@ -3,7 +3,7 @@ import androidx.room.Entity; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class Sound extends Content { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBook.java b/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBook.java index 4ef1ba3..8ad9369 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBook.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBook.java @@ -6,7 +6,7 @@ import ai.elimu.model.v2.enums.ReadingLevel; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class StoryBook extends Content { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookChapter.java b/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookChapter.java index aa5e460..f5147ad 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookChapter.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookChapter.java @@ -4,7 +4,7 @@ import androidx.room.Entity; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class StoryBookChapter extends BaseEntity { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookParagraph.java b/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookParagraph.java index c6e4465..ef3a617 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookParagraph.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookParagraph.java @@ -4,7 +4,7 @@ import androidx.room.Entity; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class StoryBookParagraph extends BaseEntity { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookParagraph_Word.java b/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookParagraph_Word.java index e49c2d5..7cfeb6e 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookParagraph_Word.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/StoryBookParagraph_Word.java @@ -4,7 +4,7 @@ import androidx.room.Entity; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity(primaryKeys = {"StoryBookParagraph_id", "words_ORDER"}) public class StoryBookParagraph_Word { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Video.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Video.java index e2b12ef..3e6ac71 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Video.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Video.java @@ -6,7 +6,7 @@ import ai.elimu.model.v2.enums.content.VideoFormat; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class Video extends Content { diff --git a/app/src/main/java/ai/elimu/content_provider/room/entity/Word.java b/app/src/main/java/ai/elimu/content_provider/room/entity/Word.java index 6978c89..8d2a697 100644 --- a/app/src/main/java/ai/elimu/content_provider/room/entity/Word.java +++ b/app/src/main/java/ai/elimu/content_provider/room/entity/Word.java @@ -6,7 +6,7 @@ import ai.elimu.model.v2.enums.content.WordType; /** - * For documentation, see https://github.com/elimu-ai/webapp/tree/master/src/main/java/ai/elimu/model + * For documentation, see https://github.com/elimu-ai/webapp/tree/main/src/main/java/ai/elimu/model */ @Entity public class Word extends Content { diff --git a/app/src/main/java/ai/elimu/content_provider/ui/letter_sound/LetterSoundsFragment.java b/app/src/main/java/ai/elimu/content_provider/ui/letter_sound/LetterSoundsFragment.java new file mode 100644 index 0000000..8412b85 --- /dev/null +++ b/app/src/main/java/ai/elimu/content_provider/ui/letter_sound/LetterSoundsFragment.java @@ -0,0 +1,145 @@ +package ai.elimu.content_provider.ui.letter_sound; + +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ProgressBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; + +import com.google.android.material.snackbar.Snackbar; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import ai.elimu.content_provider.BaseApplication; +import ai.elimu.content_provider.R; +import ai.elimu.content_provider.rest.LetterSoundsService; +import ai.elimu.content_provider.room.GsonToRoomConverter; +import ai.elimu.content_provider.room.dao.LetterSoundDao; +import ai.elimu.content_provider.room.db.RoomDb; +import ai.elimu.content_provider.room.entity.LetterSound; +import ai.elimu.model.v2.gson.content.LetterSoundGson; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +public class LetterSoundsFragment extends Fragment { + + private LetterSoundsViewModel letterSoundsViewModel; + + private ProgressBar progressBar; + + private TextView textView; + + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Log.i(getClass().getName(), "onCreateView"); + + letterSoundsViewModel = new ViewModelProvider(this).get(LetterSoundsViewModel.class); + View root = inflater.inflate(R.layout.fragment_letter_sounds, container, false); + progressBar = root.findViewById(R.id.progress_bar_letter_sounds); + textView = root.findViewById(R.id.text_letter_sounds); + letterSoundsViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() { + @Override + public void onChanged(@Nullable String s) { + Log.i(getClass().getName(), "onChanged"); + textView.setText(s); + } + }); + return root; + } + + @Override + public void onStart() { + Log.i(getClass().getName(), "onStart"); + super.onStart(); + + // Download LetterSounds from REST API, and store them in the database + BaseApplication baseApplication = (BaseApplication) getActivity().getApplication(); + Retrofit retrofit = baseApplication.getRetrofit(); + LetterSoundsService letterSoundsService = retrofit.create(LetterSoundsService.class); + Call<List<LetterSoundGson>> letterSoundGsonsCall = letterSoundsService.listLetterSounds(); + Log.i(getClass().getName(), "letterSoundGsonsCall.request(): " + letterSoundGsonsCall.request()); + letterSoundGsonsCall.enqueue(new Callback<List<LetterSoundGson>>() { + + @Override + public void onResponse(Call<List<LetterSoundGson>> call, Response<List<LetterSoundGson>> response) { + Log.i(getClass().getName(), "onResponse"); + + Log.i(getClass().getName(), "response: " + response); + if (response.isSuccessful()) { + List<LetterSoundGson> letterSoundGsons = response.body(); + Log.i(getClass().getName(), "letterSoundGsons.size(): " + letterSoundGsons.size()); + + if (letterSoundGsons.size() > 0) { + processResponseBody(letterSoundGsons); + } + } else { + // Handle error + Snackbar.make(textView, response.toString(), Snackbar.LENGTH_LONG) + .setBackgroundTint(getResources().getColor(R.color.deep_orange_darken_4)) + .show(); + progressBar.setVisibility(View.GONE); + } + } + + @Override + public void onFailure(Call<List<LetterSoundGson>> call, Throwable t) { + Log.e(getClass().getName(), "onFailure", t); + + Log.e(getClass().getName(), "t.getCause():", t.getCause()); + + // Handle error + Snackbar.make(textView, t.getCause().toString(), Snackbar.LENGTH_LONG) + .setBackgroundTint(getResources().getColor(R.color.deep_orange_darken_4)) + .show(); + progressBar.setVisibility(View.GONE); + } + }); + } + + private void processResponseBody(List<LetterSoundGson> letterSoundGsons) { + Log.i(getClass().getName(), "processResponseBody"); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + executorService.execute(new Runnable() { + @Override + public void run() { + Log.i(getClass().getName(), "run"); + + RoomDb roomDb = RoomDb.getDatabase(getContext()); + LetterSoundDao letterSoundDao = roomDb.letterSoundDao(); + + // Empty the database table before downloading up-to-date content + letterSoundDao.deleteAll(); + + for (LetterSoundGson letterSoundGson : letterSoundGsons) { + Log.i(getClass().getName(), "letterSoundGson.getId(): " + letterSoundGson.getId()); + + // Store the LetterSound in the database + LetterSound letterSound = GsonToRoomConverter.getLetterSound(letterSoundGson); + letterSoundDao.insert(letterSound); + Log.i(getClass().getName(), "Stored LetterSound in database with ID " + letterSound.getId()); + } + + // Update the UI + List<LetterSound> letterSounds = letterSoundDao.loadAll(); + Log.i(getClass().getName(), "letterSounds.size(): " + letterSounds.size()); + getActivity().runOnUiThread(() -> { + textView.setText("letterSounds.size(): " + letterSounds.size()); + Snackbar.make(textView, "letterSounds.size(): " + letterSounds.size(), Snackbar.LENGTH_LONG).show(); + progressBar.setVisibility(View.GONE); + }); + } + }); + } +} diff --git a/app/src/main/java/ai/elimu/content_provider/ui/letter_sound/LetterSoundsViewModel.java b/app/src/main/java/ai/elimu/content_provider/ui/letter_sound/LetterSoundsViewModel.java new file mode 100644 index 0000000..40e0f9b --- /dev/null +++ b/app/src/main/java/ai/elimu/content_provider/ui/letter_sound/LetterSoundsViewModel.java @@ -0,0 +1,19 @@ +package ai.elimu.content_provider.ui.letter_sound; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +public class LetterSoundsViewModel extends ViewModel { + + private MutableLiveData<String> text; + + public LetterSoundsViewModel() { + text = new MutableLiveData<>(); + text.setValue("LetterSoundsViewModel"); + } + + public LiveData<String> getText() { + return text; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_menu_emoji_symbols.xml b/app/src/main/res/drawable/ic_menu_emoji_symbols.xml new file mode 100644 index 0000000..21bc0b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_emoji_symbols.xml @@ -0,0 +1,11 @@ +<vector android:height="24dp" android:tint="#000000" + android:viewportHeight="24" android:viewportWidth="24" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="@android:color/white" android:pathData="M3,2h8v2h-8z"/> + <path android:fillColor="@android:color/white" android:pathData="M6,11l2,0l0,-4l3,0l0,-2l-8,0l0,2l3,0z"/> + <path android:fillColor="@android:color/white" android:pathData="M12.4036,20.1819l7.7781,-7.7781l1.4142,1.4142l-7.7781,7.7781z"/> + <path android:fillColor="@android:color/white" android:pathData="M14.5,14.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/> + <path android:fillColor="@android:color/white" android:pathData="M19.5,19.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0"/> + <path android:fillColor="@android:color/white" android:pathData="M15.5,11c1.38,0 2.5,-1.12 2.5,-2.5V4h3V2h-4v4.51C16.58,6.19 16.07,6 15.5,6C14.12,6 13,7.12 13,8.5C13,9.88 14.12,11 15.5,11z"/> + <path android:fillColor="@android:color/white" android:pathData="M9.74,15.96l-1.41,1.41l-0.71,-0.71l0.35,-0.35c0.98,-0.98 0.98,-2.56 0,-3.54c-0.49,-0.49 -1.13,-0.73 -1.77,-0.73c-0.64,0 -1.28,0.24 -1.77,0.73c-0.98,0.98 -0.98,2.56 0,3.54l0.35,0.35l-1.06,1.06c-0.98,0.98 -0.98,2.56 0,3.54C4.22,21.76 4.86,22 5.5,22s1.28,-0.24 1.77,-0.73l1.06,-1.06l1.41,1.41l1.41,-1.41l-1.41,-1.41l1.41,-1.41L9.74,15.96zM5.85,14.2c0.12,-0.12 0.26,-0.15 0.35,-0.15s0.23,0.03 0.35,0.15c0.19,0.2 0.19,0.51 0,0.71l-0.35,0.35L5.85,14.9C5.66,14.71 5.66,14.39 5.85,14.2zM5.85,19.85C5.73,19.97 5.59,20 5.5,20s-0.23,-0.03 -0.35,-0.15c-0.19,-0.19 -0.19,-0.51 0,-0.71l1.06,-1.06l0.71,0.71L5.85,19.85z"/> +</vector> diff --git a/app/src/main/res/layout/fragment_letter_sounds.xml b/app/src/main/res/layout/fragment_letter_sounds.xml new file mode 100644 index 0000000..65c9187 --- /dev/null +++ b/app/src/main/res/layout/fragment_letter_sounds.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin"> + + <ProgressBar + android:id="@+id/progress_bar_letter_sounds" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> + + <TextView + android:id="@+id/text_letter_sounds" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="center" + android:textSize="20sp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index 5ce4299..0a429f4 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -21,6 +21,10 @@ android:id="@+id/nav_sounds" android:icon="@drawable/ic_menu_music_note" android:title="@string/menu_sounds" /> + <item + android:id="@+id/nav_letter_sounds" + android:icon="@drawable/ic_menu_emoji_symbols" + android:title="@string/menu_letter_sounds" /> <item android:id="@+id/nav_words" android:icon="@drawable/ic_menu_sms" diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 4ac4988..2e50674 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -23,6 +23,12 @@ android:label="@string/menu_sounds" tools:layout="@layout/fragment_sounds" /> + <fragment + android:id="@+id/nav_letter_sounds" + android:name="ai.elimu.content_provider.ui.letter_sound.LetterSoundsFragment" + android:label="@string/menu_letter_sounds" + tools:layout="@layout/fragment_letter_sounds" /> + <fragment android:id="@+id/nav_words" android:name="ai.elimu.content_provider.ui.word.WordsFragment" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 845193c..49fd9cf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ <string name="menu_home">Home</string> <string name="menu_letters">Letters</string> + <string name="menu_letter_sounds">Letter-Sounds</string> <string name="menu_sounds">Sounds</string> <string name="menu_words">Words</string> <string name="menu_numbers">Numbers</string>