Skip to content
2 changes: 2 additions & 0 deletions Mani-Quotify/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ dependencies {
implementation(libs.androidx.ui.navigation)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.retrofit)
implementation(libs.converter.gson)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use kotlin serialization?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved

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
15 changes: 14 additions & 1 deletion Mani-Quotify/app/src/main/java/com/mani/quotify007/QuotifyApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,34 @@ package com.mani.quotify007
import android.app.Application
import androidx.room.Room
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.usecase.GetQuoteUseCase
import com.mani.quotify007.ui.screens.utils.QUOTIFY_DB_NAME
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

private const val BASE_URL = "https://api.quotable.io/"
class QuotifyApp: Application() {
lateinit var api: QuoteApiService
lateinit var quoteDb: QuotifyDatabase
lateinit var getQuoteUseCase: GetQuoteUseCase
override fun onCreate() {
super.onCreate()
api = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getSafeOkHttpClient(this))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(QuoteApiService::class.java)

quoteDb = Room.databaseBuilder(
applicationContext,
QuotifyDatabase::class.java,
QUOTIFY_DB_NAME
).build()
val quoteRepository = QuoteRepositoryImpl(quoteDb.favoriteQuoteDao())
val quoteRepository = QuoteRepositoryImpl(this)
getQuoteUseCase = GetQuoteUseCase(repository = quoteRepository)
}
}
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.domain.model.QuoteResult
import retrofit2.http.GET

interface QuoteApiService {
@GET("quotes")
suspend fun getQuoteResult(): QuoteResult
}
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
@@ -1,73 +1,43 @@
package com.mani.quotify007.data.repository

import com.mani.quotify007.data.local.FavoriteQuoteDao
import com.mani.quotify007.QuotifyApp
import com.mani.quotify007.data.local.FavoriteQuoteEntity
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 application: QuotifyApp) : QuoteRepository {

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

override fun getQuotes(): List<Quote> = quotesDataList

//TODO: Future implementation
override fun addQuote(quote: Quote) {
quotes.add(quote)
quotesDataList.add(quote)
}

//TODO: Future implementation
override fun removeQuote(quote: Quote) {
quotes.remove(quote)
quotesDataList.remove(quote)
}

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

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

override suspend fun removeFavoriteQuote(quote: Quote) {
favoriteQuoteDao.deleteFavoriteQuote(quote.toEntity())
application.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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.mani.quotify007.data.repository

import com.mani.quotify007.domain.model.Quote

val quotesDataList = mutableListOf(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we still need these?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved

Quote(
_id = "111",
content = "The greatest glory in living lies not in never falling, " +
"but in rising every time we fall.",
author = "Nelson Mandela"
),
Quote(
_id = "222",
content = "The way to get started is to quit talking and begin doing.",
author = "Walt Disney"
),
Quote(
_id = "333",
content = "Your time is limited, so don't waste it living someone else's life.",
author = "Steve Jobs"
),
Quote(
_id = "444",
content = "If life were predictable it would cease to be life, and be without flavor.",
author = "Eleanor Roosevelt"
),
Quote(
_id = "555",
content = "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.",
author = "Oprah Winfrey"
),
Quote(
_id = "666",
content = "If you set your goals ridiculously high and it's a failure, " +
"you will fail above everyone else's success.",
author = "James Cameron"
)
)
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package com.mani.quotify007.domain.model

data class Quote(
val id: Int,
val text: String,
val author: String? = null,
val _id: String,
val author: String,
val authorSlug: String? = null,
val content: String,
val dateAdded: String? = null,
val dateModified: String? = null,
val length: Int? = null,
val tags: List<String>? = null,
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: Int,
val lastItemIndex: Int,
val page: Int,
val results: List<Quote>,
val totalCount: Int,
val totalPages: Int
)
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
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 {
suspend fun getQuoteResult(): QuoteResult
fun getQuotes(): List<Quote>
fun addQuote(quote: Quote)
fun removeQuote(quote: Quote)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
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) {
suspend fun result(): QuoteResult = repository.getQuoteResult()
// TODO: Use when static quote data used
fun execute(): List<Quote> = repository.getQuotes()
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.mani.quotify007.ui.navigation.MainScreen
import com.mani.quotify007.ui.navigation.viewmodel.MainViewModel
import com.mani.quotify007.ui.screens.utils.onCopyText
import com.mani.quotify007.ui.screens.utils.shareQuote
import com.mani.quotify007.ui.screens.utils.showToast
import com.mani.quotify007.ui.theme.QuotifyAppTheme

class MainActivity : ComponentActivity() {
Expand All @@ -23,11 +24,18 @@ class MainActivity : ComponentActivity() {
val state = viewModel.state.collectAsState().value
MainScreen(
state,
onEvent = { event -> viewModel.onEvent(event) },
onCopyText = { quote -> onCopyText(this, quote) },
onShareClick = { quote -> shareQuote(this, quote) }
onEvent = { event -> viewModel.onEvent(event) }
)
}
}
viewModel.copyTextEvent.observe(this) { quote ->
onCopyText(this, quote)
}
viewModel.shareClickEvent.observe(this) { quote ->
shareQuote(this, quote)
}
viewModel.showToast.observe(this) { message ->
message?.let { showToast(it) }
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more better approach? looks repetative

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved

}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.mani.quotify007.ui.navigation

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.rememberNavController
import com.mani.quotify007.domain.model.Quote
import com.mani.quotify007.ui.navigation.model.MainEvent
import com.mani.quotify007.ui.navigation.model.MainState
import com.mani.quotify007.ui.screens.bottomappbar.BottomAppBar
Expand All @@ -15,24 +17,23 @@ import com.mani.quotify007.ui.theme.QuotifyAppTheme
@Composable
fun MainScreen(
state: MainState,
onEvent: (MainEvent) -> Unit,
onCopyText: (Quote) -> Unit,
onShareClick: (Quote) -> Unit
onEvent: (MainEvent) -> Unit
) {
val navController = rememberNavController()
Scaffold(
bottomBar = { BottomAppBar(navController) }
) { innerPadding ->
NavigationGraph(
navController = navController,
modifier = Modifier.padding(innerPadding),
state = state,
quotes = state.quotes,
favoriteQuotes = state.favoriteQuotes,
onEvent = onEvent,
onCopyText = onCopyText,
onShareClick = onShareClick
)
Box {
if (state.isLoading) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
NavigationGraph(
navController = navController,
modifier = Modifier.padding(innerPadding),
state = state,
onEvent = onEvent
)
}
}
}

Expand All @@ -42,9 +43,7 @@ fun MainScreenPreview() {
QuotifyAppTheme {
MainScreen(
state = MainState(),
onEvent = {},
onCopyText = {},
onShareClick = {}
onEvent = {}
)
}
}
Loading