Skip to content
5 changes: 5 additions & 0 deletions Mani-Quotify/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.androidApplication)
alias(libs.plugins.jetbrainsKotlinAndroid)
alias(libs.plugins.ksp)
alias(libs.plugins.kotlinSerialization)
}

android {
Expand Down Expand Up @@ -63,6 +64,10 @@ dependencies {
implementation(libs.androidx.ui.navigation)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.retrofit)
implementation(libs.ktx.sxn.json)
implementation(libs.ktx.sxn.converter)
implementation(libs.okhttp)
ksp(libs.androidx.room.compiler)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
Expand Down
1 change: 1 addition & 0 deletions Mani-Quotify/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".QuotifyApp"
android:allowBackup="true"
Expand Down
20 changes: 6 additions & 14 deletions Mani-Quotify/app/src/main/java/com/mani/quotify007/QuotifyApp.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
package com.mani.quotify007

import android.app.Application
import androidx.room.Room
import com.mani.quotify007.data.local.QuotifyDatabase
import com.mani.quotify007.data.repository.QuoteRepositoryImpl
import com.mani.quotify007.domain.usecase.GetQuoteUseCase
import com.mani.quotify007.ui.screens.utils.QUOTIFY_DB_NAME

class QuotifyApp: Application() {
lateinit var quoteDb: QuotifyDatabase
lateinit var getQuoteUseCase: GetQuoteUseCase
companion object {
/* Hold a static reference to the AppModule instance, which can be accessed from anywhere
* in the application without needing an instance of the Application class */
lateinit var instance: QuotiyAppModule
}
override fun onCreate() {
super.onCreate()
quoteDb = Room.databaseBuilder(
applicationContext,
QuotifyDatabase::class.java,
QUOTIFY_DB_NAME
).build()
val quoteRepository = QuoteRepositoryImpl(quoteDb.favoriteQuoteDao())
getQuoteUseCase = GetQuoteUseCase(repository = quoteRepository)
instance = QuotiyAppModuleImpl(this)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.mani.quotify007

import androidx.room.Room
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import com.mani.quotify007.data.local.FavoriteQuoteDao
import com.mani.quotify007.data.local.QuotifyDatabase
import com.mani.quotify007.data.remote.QuoteApiService
import com.mani.quotify007.data.remote.getSafeOkHttpClient
import com.mani.quotify007.data.repository.QuoteRepositoryImpl
import com.mani.quotify007.domain.repository.QuoteRepository
import com.mani.quotify007.domain.usecase.GetQuoteUseCase
import com.mani.quotify007.ui.screens.utils.BASE_URL
import com.mani.quotify007.ui.screens.utils.QUOTIFY_DB_NAME
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import retrofit2.Retrofit

interface QuotiyAppModule {
val retrofit: Retrofit
val quoteDb: QuotifyDatabase
val quoteApiService: QuoteApiService
val quoteRepository: QuoteRepository
val quoteUseCase: GetQuoteUseCase
val quoteFavoriteQuoteDao: FavoriteQuoteDao
}

class QuotiyAppModuleImpl(context: QuotifyApp) : QuotiyAppModule {
override val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getSafeOkHttpClient(context))
.addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
.build()
}
override val quoteDb: QuotifyDatabase by lazy {
Room.databaseBuilder(
context,
QuotifyDatabase::class.java,
QUOTIFY_DB_NAME
).build()
}

override val quoteApiService: QuoteApiService by lazy {
retrofit.create(QuoteApiService::class.java)
}
override val quoteRepository: QuoteRepository by lazy {
QuoteRepositoryImpl(quoteApiService, quoteDb)
}
override val quoteUseCase: GetQuoteUseCase by lazy {
GetQuoteUseCase(quoteRepository)
}
override val quoteFavoriteQuoteDao: FavoriteQuoteDao by lazy {
quoteDb.favoriteQuoteDao()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import com.mani.quotify007.ui.screens.utils.QUOTE_TABLE_NAME

@Entity(tableName = QUOTE_TABLE_NAME)
data class FavoriteQuoteEntity(
@PrimaryKey val id: Int = 0,
val text: String,
@PrimaryKey val id: String,
val content: String,
val author: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.mani.quotify007.data.remote

import com.mani.quotify007.data.remote.model.QuoteNetworkModel
import retrofit2.http.GET

interface QuoteApiService {
@GET("quotes")
suspend fun getQuoteResult(): QuoteNetworkModel
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.mani.quotify007.data.remote

import com.mani.quotify007.QuotifyApp
import com.mani.quotify007.R
import okhttp3.OkHttpClient
import java.security.KeyStore
import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManagerFactory
import javax.net.ssl.X509TrustManager

fun getSafeOkHttpClient(quotifyApp: QuotifyApp): OkHttpClient {
return try {
// Load the trusted certificate
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply {
load(null, null)
// quotable io trusted certificate
quotifyApp.resources.openRawResource(R.raw.api_quotable_io).use { certInputStream ->
val certificateFactory = java.security.cert.CertificateFactory.getInstance("X.509")
val certificate = certificateFactory.generateCertificate(certInputStream)
setCertificateEntry("api_quotable_io", certificate)
}
}

// Create a TrustManager that trusts the CAs in our KeyStore
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()).apply {
init(keyStore)
}
val trustManagers = trustManagerFactory.trustManagers
val sslContext = SSLContext.getInstance("TLS").apply {
init(null, trustManagers, java.security.SecureRandom())
}
val sslSocketFactory = sslContext.socketFactory

OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustManagers[0] as X509TrustManager)
.build()
} catch (e: Exception) {
throw RuntimeException(e)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.mani.quotify007.data.remote.mapper

import com.mani.quotify007.data.remote.model.QuoteNetworkModel
import com.mani.quotify007.domain.model.Quote
import com.mani.quotify007.domain.model.QuoteResult

fun QuoteNetworkModel.toResults() = QuoteResult(
count = count,
lastItemIndex = lastItemIndex,
page = page,
results = results.map {
Quote(
id = it.id,
author = it.author,
content = it.content,
isFavorite = false
)
},
totalCount = totalCount,
totalPages = totalPages
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.mani.quotify007.data.remote.model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class QuoteNetworkModel (
val count: Long,
val totalCount: Long,
val page: Long,
val totalPages: Long,
val lastItemIndex: Long,
val results: List<QuoteItemNetworkModel>
)

@Serializable
data class QuoteItemNetworkModel (
@SerialName("_id")
val id: String,
val author: String,
val content: String,
val tags: List<String>,
val authorSlug: String,
val length: Long,
val dateAdded: String,
val dateModified: String
)
Original file line number Diff line number Diff line change
@@ -1,73 +1,46 @@
package com.mani.quotify007.data.repository

import com.mani.quotify007.data.local.FavoriteQuoteDao
import com.mani.quotify007.data.local.FavoriteQuoteEntity
import com.mani.quotify007.data.local.QuotifyDatabase
import com.mani.quotify007.data.remote.QuoteApiService
import com.mani.quotify007.data.remote.mapper.toResults
import com.mani.quotify007.domain.model.Quote
import com.mani.quotify007.domain.model.QuoteResult
import com.mani.quotify007.domain.repository.QuoteRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

class QuoteRepositoryImpl(private val favoriteQuoteDao: FavoriteQuoteDao) : QuoteRepository {
private val quotes = mutableListOf(
Quote(
1,
"The greatest glory in living lies not in never falling, " +
"but in rising every time we fall.",
"Nelson Mandela"
),
Quote(
2,
"The way to get started is to quit talking and begin doing.",
"Walt Disney"
),
Quote(
3,
"Your time is limited, so don't waste it living someone else's life.",
"Steve Jobs"
),
Quote(
4,
"If life were predictable it would cease to be life, and be without flavor.",
"Eleanor Roosevelt"
),
Quote(
5,
"If you look at what you have in life, you'll always have more. " +
"If you look at what you don't have in life, you'll never have enough.",
"Oprah Winfrey"
),
Quote(
6,
"If you set your goals ridiculously high and it's a failure, " +
"you will fail above everyone else's success.",
"James Cameron"
)
)
class QuoteRepositoryImpl(
private val quoteApiService: QuoteApiService,
private val quoteDb: QuotifyDatabase
) : QuoteRepository {

override fun getQuotes(): List<Quote> = quotes
override suspend fun getQuoteResult(): QuoteResult =
quoteApiService.getQuoteResult().toResults()

override fun addQuote(quote: Quote) {
quotes.add(quote)
//TODO: add quote to list
}

override fun removeQuote(quote: Quote) {
quotes.remove(quote)
//TODO: remove quote from list
}

override fun getFavoriteQuotes(): Flow<List<Quote>> =
favoriteQuoteDao.getAllFavoriteQuotes().map {entities ->
override suspend fun getFavoriteQuotes(): Flow<List<Quote>> =
quoteDb.favoriteQuoteDao().getAllFavoriteQuotes().map {entities ->
entities.map { entity -> entity.toDomainModel() }
}

override suspend fun addFavoriteQuote(quote: Quote) {
favoriteQuoteDao.insertFavoriteQuote(quote.toEntity())
quoteDb.favoriteQuoteDao().insertFavoriteQuote(quote.toEntity())
}

override suspend fun removeFavoriteQuote(quote: Quote) {
favoriteQuoteDao.deleteFavoriteQuote(quote.toEntity())
quoteDb.favoriteQuoteDao().deleteFavoriteQuote(quote.toEntity())
}
}

private fun Quote.toEntity() = FavoriteQuoteEntity(id = id, text = text, author = author ?: "")
private fun Quote.toEntity() = FavoriteQuoteEntity(id = id, content = content, author = author)

private fun FavoriteQuoteEntity.toDomainModel() = Quote(id, text, author)
private fun FavoriteQuoteEntity.toDomainModel() =
Quote(id, content = content, author = author, isFavorite = true)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.mani.quotify007.domain.model

data class Quote(
val id: Int,
val text: String,
val author: String? = null,
val id: String,
val content: String,
val author: String,
var isFavorite: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.mani.quotify007.domain.model

data class QuoteResult(
val count: Long,
val lastItemIndex: Long,
val page: Long,
val results: List<Quote>,
val totalCount: Long,
val totalPages: Long
)
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.mani.quotify007.domain.repository

import com.mani.quotify007.domain.model.Quote
import com.mani.quotify007.domain.model.QuoteResult
import kotlinx.coroutines.flow.Flow

interface QuoteRepository {
fun getQuotes(): List<Quote>
interface QuoteRepository {
suspend fun getQuoteResult(): QuoteResult
fun addQuote(quote: Quote)
fun removeQuote(quote: Quote)
fun getFavoriteQuotes(): Flow<List<Quote>>
suspend fun getFavoriteQuotes(): Flow<List<Quote>>
suspend fun addFavoriteQuote(quote: Quote)
suspend fun removeFavoriteQuote(quote: Quote)
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package com.mani.quotify007.domain.usecase

import com.mani.quotify007.domain.model.Quote
import com.mani.quotify007.domain.model.QuoteResult
import com.mani.quotify007.domain.repository.QuoteRepository

class GetQuoteUseCase(private val repository: QuoteRepository) {
fun execute(): List<Quote> = repository.getQuotes()
suspend fun result(): QuoteResult = repository.getQuoteResult()
suspend fun dbQuotes() = repository.getFavoriteQuotes()
suspend fun addFavoriteQuote(quote: Quote) = repository.addFavoriteQuote(quote)
suspend fun removeFavoriteQuote(quote: Quote) = repository.removeFavoriteQuote(quote)
}
Loading