This commit is contained in:
gowthaman
2022-01-30 15:36:27 +05:30
commit 4b7c0fb1c9
51 changed files with 1596 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="my.first.learnspell">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LearnSpell">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,38 @@
package my.first.learnspell
import android.app.Application
import android.content.Context
import androidx.room.Room
import timber.log.Timber
class App : Application() {
override fun onCreate() {
super.onCreate()
Timber.plant(Timber.DebugTree())
appInstance = this
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
}
companion object {
lateinit var appInstance: App
val db: AppDatabase by lazy {
//singleton because it is expensive
Room
.databaseBuilder(
appInstance,
AppDatabase::class.java, "words-v1"
)
.build()
}
}
}

View File

@@ -0,0 +1,246 @@
package my.first.learnspell
import android.content.pm.PackageManager
import android.media.MediaPlayer
import android.media.MediaRecorder
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import my.first.learnspell.databinding.ActivityMainBinding
import timber.log.Timber
import java.io.IOException
import java.lang.IllegalStateException
import java.util.*
import java.util.concurrent.CopyOnWriteArrayList
class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
lateinit var binding: ActivityMainBinding
lateinit var playStatusModel: PlayStatusModel
lateinit var sharedViewModel: SharedViewModel
lateinit var wordsViewModel: WordsViewModel
val words: MutableList<Word> = CopyOnWriteArrayList<Word>()
val allWords: MutableList<Word> = CopyOnWriteArrayList<Word>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
sharedViewModel = ViewModelProvider(this).get(SharedViewModel::class.java)
wordsViewModel = ViewModelProvider(this).get(WordsViewModel::class.java)
playStatusModel = ViewModelProvider(this).get(PlayStatusModel::class.java)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
setMapView()
}
}
override fun onResume() {
super.onResume()
requestPermissionsIfNecessary(
listOf(
android.Manifest.permission.RECORD_AUDIO,
android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
)
)
}
private fun requestPermissionsIfNecessary(permissions: List<String>) {
val permissionsToRequest: MutableList<String> = arrayListOf()
for (permission in permissions) {
if (ContextCompat.checkSelfPermission(this, permission)
!= PackageManager.PERMISSION_GRANTED
) {
permissionsToRequest.add(permission)
}
}
if (permissionsToRequest.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
permissionsToRequest.toTypedArray(),
100
)
} else {
setMapView()
}
}
var fileName: String? = null
var fileNameToPlay: String? = null
var currPos: Int = -1
private fun setMapView() {
binding.recordBtn.setOnClickListener {
val stopIcon = ContextCompat.getDrawable(
this,
R.drawable.ic_baseline_stop_circle_24
)
val recordIcon = ContextCompat.getDrawable(
this,
R.drawable.ic_baseline_mic_24
)
if (recorder == null) {
fileName = "${externalCacheDir?.absolutePath}/${UUID.randomUUID()}.3gp"
startRecording()
binding.recordBtn.icon = stopIcon
binding.recordBtn.text = "Save"
} else {
stopRecording()
binding.recordBtn.icon = recordIcon
binding.recordBtn.text = "Record"
//save to DB
wordsViewModel.saveToDB(
Word(
null, binding.searchWord.text.toString(), fileName!!, actionAt = Date()
)
)
binding.searchWord.setText("")
fileName = null
}
}
sharedViewModel.name.observe(this) { ph ->
//play control
Timber.w("Play/Stop [${ph.first}][${ph.second}]")
if (ph.first > -1) {
currPos = ph.first
fileNameToPlay = ph.second
startPlaying()
} else {
fileNameToPlay = null
currPos = -1
stopPlaying()
}
}
binding.searchWord.addTextChangedListener(
object : TextWatcher {
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun afterTextChanged(p0: Editable?) {
val other = p0?.toString()?.lowercase() ?: return
binding.existingList.adapter = WordListAdapter(
this@MainActivity,
words.filter { it.word.lowercase().contains(other) },
sharedViewModel, playStatusModel
)
}
}
)
wordsViewModel.loadFromDB()
wordsViewModel.words.observe(this) { w ->
allWords.clear()
words.clear()
allWords.addAll(w)
words.addAll(allWords)
binding.existingList.adapter =
WordListAdapter(this, words, sharedViewModel, playStatusModel)
}
binding.existingList.layoutManager = LinearLayoutManager(this)
}
private var recorder: MediaRecorder? = null
private var player: MediaPlayer? = null
private fun startPlaying() {
playStatusModel.sendName(currPos, true)
binding.existingList.adapter?.notifyItemChanged(currPos)
player = MediaPlayer().apply {
try {
setDataSource(fileNameToPlay)
prepare()
start()
setOnCompletionListener {
Timber.w("Play Completed...$currPos")
playStatusModel.sendName(currPos, false)
binding.existingList.adapter?.notifyItemChanged(currPos)
it?.reset()
it?.release()
}
} catch (e: IOException) {
Timber.w(e, "prepare() failed")
}
}
}
private fun stopPlaying() {
player?.reset()
player?.release()
player = null
}
private fun startRecording() {
recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
setOutputFile(fileName)
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
try {
prepare()
start()
Timber.w("Saving to $fileName")
} catch (e: IOException) {
Timber.e(e, "prepare() failed")
}
}
}
private fun stopRecording() {
recorder?.apply {
try {
stop()
reset()
release()
} catch (e: IllegalStateException) {
Timber.w(e, "error in stop")
}
}
recorder = null
}
}

View File

@@ -0,0 +1,76 @@
package my.first.learnspell
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.google.android.material.textview.MaterialTextView
import timber.log.Timber
class WordListAdapter(
private val context: Context,
private val words: List<Word>,
private val viewModel: SharedViewModel,
private val playStatus: PlayStatusModel
) : RecyclerView.Adapter<WordListAdapter.WordHolder>() {
inner class WordHolder internal constructor(itemView: View) :
RecyclerView.ViewHolder(itemView) {
val wordName = itemView.findViewById<MaterialTextView>(R.id.word)
val playBtn = itemView.findViewById<MaterialButton>(R.id.playBtn)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordHolder {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val view = inflater.inflate(R.layout.word_display, parent, false)
return WordHolder(view)
}
override fun onBindViewHolder(holder: WordHolder, position: Int) {
val call = words[position]
holder.wordName.text = call.word
val stopIcon = ContextCompat.getDrawable(
this.context,
R.drawable.ic_baseline_stop_circle_24
)
val playIcon = ContextCompat.getDrawable(
this.context,
R.drawable.ic_baseline_play_circle_outline_24
)
Timber.w("Set Icon $position => ${isPlaying(position)}")
if (isPlaying(position)) {
holder.playBtn.icon = stopIcon
holder.playBtn.text = "Stop"
} else {
holder.playBtn.icon = playIcon
holder.playBtn.text = "Play"
}
holder.playBtn.setOnClickListener {
if (isPlaying(position)) {
viewModel.sendName(Pair(position, ""))
} else {
viewModel.sendName(Pair(position, call.sound))
}
}
}
private fun isPlaying(position: Int): Boolean {
val value = playStatus.name.value
return value != null && value.containsKey(position) && value[position]!!
}
override fun getItemCount(): Int {
return words.size
}
}

View File

@@ -0,0 +1,45 @@
package my.first.learnspell
import androidx.room.*
import java.util.*
@Entity(tableName = "words")
data class Word(
@PrimaryKey val uid: Int? = null,
@ColumnInfo(name = "word") val word: String,
@ColumnInfo(name = "sound") val sound: String,
@ColumnInfo(name = "recorded_at") var actionAt: Date,
)
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
@Dao
interface WordDAO {
@Query("select * from words order by recorded_at desc")
suspend fun all(): List<Word>
@Insert
suspend fun saveAll(vararg logs: Word)
@Update
suspend fun update(vararg logs: Word)
}
@Database(
entities = [Word::class], version = 1
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun wordDAO(): WordDAO
}

View File

@@ -0,0 +1,51 @@
package my.first.learnspell
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import timber.log.Timber
class WordsViewModel : ViewModel() {
// Make a network request without blocking the UI thread
val words = MutableLiveData<List<Word>>()
fun loadFromDB() {
// launch a coroutine in viewModelScope
viewModelScope.launch {
words.value = App.db.wordDAO().all()
}
}
fun saveToDB(word: Word) {
viewModelScope.launch {
App.db.wordDAO().saveAll(word)
words.value = App.db.wordDAO().all()
}
}
// No need to override onCleared()
}
class SharedViewModel : ViewModel() {
val name = MutableLiveData<Pair<Int, String>>()
fun sendName(text: Pair<Int, String>) {
name.value = text
}
}
class PlayStatusModel : ViewModel() {
val name = MutableLiveData<MutableMap<Int, Boolean>>()
fun sendName(pos: Int, st: Boolean) {
val m: MutableMap<Int, Boolean> = hashMapOf()
name.value?.forEach { (t, u) ->
m[t] = u
}
m[pos] = st
name.value = m
Timber.w("Set $pos => $st ==> ${name.value}")
}
}

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M8,16h8V8H8V16zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2L12,2z"
android:fillType="evenOdd"/>
</vector>

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@@ -0,0 +1,47 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/textInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="24dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/searchWord"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:hint="Enter Word" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/recordBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:text="Record"
app:icon="@drawable/ic_baseline_mic_24"
app:layout_constraintBottom_toBottomOf="@+id/textInputLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/textInputLayout" />
<androidx.recyclerview.widget.RecyclerView
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textInputLayout"
android:id="@+id/existingList"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,30 @@
<?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="wrap_content">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/word"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="Word"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/playBtn"
app:icon="@drawable/ic_baseline_play_circle_outline_24"
android:text="Play"
android:layout_marginEnd="8dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@id/word"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/word" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.LearnSpell" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@@ -0,0 +1,3 @@
<resources>
<string name="app_name">Learn Spell</string>
</resources>

View File

@@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.LearnSpell" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>