Skip to content

Commit

Permalink
Redesign + new logic + more news
Browse files Browse the repository at this point in the history
  • Loading branch information
LinX64 committed Oct 21, 2023
1 parent 88eb76c commit 11746b3
Show file tree
Hide file tree
Showing 23 changed files with 409 additions and 124 deletions.
2 changes: 2 additions & 0 deletions androidApp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.client.news

import NewsApp
import ui.NewsApp
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
Expand Down

This file was deleted.

6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jetbrains-atomicfu = "0.22.0"
koin = "3.5.0"
koin-compose = "1.1.0"

kotlinxSerializationJson = "1.6.0"
ktorVersion = "2.3.5"
loggingInterceptor = "4.12.0"
mokoMvvmVersion = "0.16.1"
retrofit = "2.9.0"
Expand Down Expand Up @@ -64,6 +66,10 @@ androidx-dataStore-preferences = { group = "androidx.datastore", name = "datasto
jetbrains-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version.ref = "jetbrains-atomicfu" }
coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" }
coil-gif = { group = "io.coil-kt", name = "coil-gif", version.ref = "coil" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktorVersion" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktorVersion" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktorVersion" }
logging-interceptor-4_9_1 = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" }
mlKit-Text-Recognition = { group = "com.google.android.gms", name = "play-services-mlkit-text-recognition", version.ref = "mlKitTextRecognition" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
Expand Down
24 changes: 13 additions & 11 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import org.jetbrains.compose.ExperimentalComposeLibrary

plugins {
kotlin("multiplatform")
kotlin("plugin.serialization") version "1.9.0"
kotlin("native.cocoapods")
id("com.android.library")
id("org.jetbrains.compose")
Expand Down Expand Up @@ -29,26 +30,27 @@ kotlin {
sourceSets {
val commonMain by getting {
dependencies {
// Compose
implementation(compose.material3)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.ui)
implementation(compose.materialIconsExtended)

@OptIn(ExperimentalComposeLibrary::class)
implementation(compose.components.resources)
implementation(libs.jetbrains.atomicfu)

implementation(libs.koin.core)
implementation(libs.koin.compose)

implementation(libs.moko.core)
implementation(libs.moko.flow)
implementation(libs.ktor.client.core)

implementation(libs.kotlinx.serialization.json)
}
}
val androidMain by getting {
dependencies {
// Compose
api(libs.androidx.activity.compose)
api(libs.androidx.appcompat)
api(libs.androidx.core.ktx)
Expand All @@ -59,16 +61,13 @@ kotlin {
api(libs.androidx.compose.runtime)
api(libs.androidx.lifecycle.runtimeCompose)

// Koin
implementation(libs.koin.android)
implementation(libs.retrofit.core)
implementation(libs.gson.converter)
implementation(libs.okhttp.logging)
implementation(libs.kotlinx.coroutines.android)

// Retrofit
api(libs.retrofit.core)
api(libs.gson.converter)
api(libs.okhttp.logging)

// Coroutines
api(libs.kotlinx.coroutines.android)
implementation(libs.ktor.client.okhttp)
}
}
val iosX64Main by getting
Expand All @@ -79,6 +78,9 @@ kotlin {
iosX64Main.dependsOn(this)
iosArm64Main.dependsOn(this)
iosSimulatorArm64Main.dependsOn(this)
dependencies {
implementation(libs.ktor.client.darwin)
}
}
}
}
Expand Down
1 change: 1 addition & 0 deletions shared/src/androidMain/kotlin/main.android.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import androidx.compose.runtime.Composable
import ui.NewsApp

@Composable
fun MainView() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package data.dataSource

import data.model.TopHeadlinesResponse

interface NewsDataSource {
suspend fun getTopHeadlines(): TopHeadlinesResponse
suspend fun getWallStreetJournal(): TopHeadlinesResponse
}
27 changes: 27 additions & 0 deletions shared/src/commonMain/kotlin/data/dataSource/NewsDataSourceImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package data.dataSource

import data.model.TopHeadlinesResponse
import data.util.Consts
import data.util.Sources
import data.util.Sources.WALL_STREET_JOURNAL
import io.ktor.client.HttpClient
import io.ktor.client.request.get
import io.ktor.client.statement.bodyAsText
import kotlinx.serialization.json.Json

class NewsDataSourceImpl(private val client: HttpClient) : NewsDataSource {

override suspend fun getTopHeadlines(): TopHeadlinesResponse {
val response = getSpecificNewsBySource(Sources.TechCrunch.value)
return Json.decodeFromString(response)
}

override suspend fun getWallStreetJournal(): TopHeadlinesResponse {
val response = getSpecificNewsBySource(WALL_STREET_JOURNAL.value)
return Json.decodeFromString(response)
}

private suspend fun getSpecificNewsBySource(source: String): String {
return client.get(Consts.BASE_URL + source + Consts.API_KEY).bodyAsText()
}
}
42 changes: 42 additions & 0 deletions shared/src/commonMain/kotlin/data/model/TopHeadlinesResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package data.model

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

@Serializable
data class TopHeadlinesResponse(
@SerialName("articles")
val articles: List<Article>,
@SerialName("status")
val status: String,
@SerialName("totalResults")
val totalResults: Int
)

@Serializable
data class Article(
@SerialName("author")
val author: String,
@SerialName("content")
val content: String,
@SerialName("description")
val description: String,
@SerialName("publishedAt")
val publishedAt: String,
@SerialName("source")
val source: Source,
@SerialName("title")
val title: String,
@SerialName("url")
val url: String,
@SerialName("urlToImage")
val urlToImage: String
)

@Serializable
data class Source(
@SerialName("id")
val id: String,
@SerialName("name")
val name: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package data.repository

import data.model.TopHeadlinesResponse
import kotlinx.coroutines.flow.Flow

interface NewsRepository {
fun getTopHeadlines(): Flow<TopHeadlinesResponse>
fun getTopHeadlinesByTopic(topic: String): Flow<TopHeadlinesResponse>
}
28 changes: 28 additions & 0 deletions shared/src/commonMain/kotlin/data/repository/NewsRepositoryImpl.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package data.repository

import data.dataSource.NewsDataSource
import data.dataSource.NewsDataSourceImpl
import data.model.TopHeadlinesResponse
import data.util.Sources
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn

class NewsRepositoryImpl(
private val newsDataSource: NewsDataSource,
private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO
) : NewsRepository {

override fun getTopHeadlines(): Flow<TopHeadlinesResponse> = flow {
val topHeadlines = newsDataSource.getTopHeadlines()
emit(topHeadlines)
}.flowOn(ioDispatcher)

override fun getTopHeadlinesByTopic(topic: String): Flow<TopHeadlinesResponse> = flow {
val topHeadlines = newsDataSource.getTopHeadlines()
emit(topHeadlines)
}.flowOn(ioDispatcher)
}
6 changes: 6 additions & 0 deletions shared/src/commonMain/kotlin/data/util/Consts.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package data.util

object Consts {
const val BASE_URL = "https://newsapi.org/v2/"
const val API_KEY = "048261763a0240babf29ff0a2e567780"
}
21 changes: 21 additions & 0 deletions shared/src/commonMain/kotlin/data/util/Result.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package data.util

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart

sealed interface Result<out T> {
data class Success<T>(val data: T) : Result<T>
data class Error(val exception: Throwable? = null) : Result<Nothing>
data object Loading : Result<Nothing>
}

fun <T> Flow<T>.asResult(): Flow<Result<T>> {
return this
.map<T, Result<T>> {
Result.Success(it)
}
.onStart { emit(Result.Loading) }
.catch { emit(Result.Error(it)) }
}
9 changes: 9 additions & 0 deletions shared/src/commonMain/kotlin/data/util/Sources.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package data.util

enum class Sources(val value: String) {
TechCrunch("top-headlines?sources=techcrunch&apiKey="),
Business("top-headlines?country=us&category=business&apiKey="),
WALL_STREET_JOURNAL("everything?domains=wsj.com&apiKey="),
TESLA("everything?q=tesla&from=2023-09-21&sortBy=publishedAt&apiKey="),
APPLE("everything?q=apple&from=2023-10-20&to=2023-10-20&sortBy=popularity&apiKey=")
}
18 changes: 14 additions & 4 deletions shared/src/commonMain/kotlin/di/Modules.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
package di

import data.repository.MainRepositoryImpl
import data.dataSource.NewsDataSource
import data.dataSource.NewsDataSourceImpl
import data.repository.NewsRepository
import data.repository.NewsRepositoryImpl
import io.ktor.client.HttpClient
import org.koin.dsl.module
import ui.MainViewModel

fun appModule() = module {
single { MainRepositoryImpl() }
val appModule = module {
viewModelDefinition { MainViewModel(get()) }
}

viewModelDefinition { MainViewModel() }
val networkModule = module {
factory { HttpClient() }
single<NewsDataSource> { NewsDataSourceImpl(get()) }

// Repository
single<NewsRepository> { NewsRepositoryImpl(get()) }
}
Loading

0 comments on commit 11746b3

Please sign in to comment.