Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.shifthackz.joyreactor

import android.app.Application
import com.shifthackz.joyreactor.data.di.dataModule
import com.shifthackz.joyreactor.domain.di.domainModule
import com.shifthackz.joyreactor.network.di.networkModule
import com.shifthackz.joyreactor.presentation.di.viewModelModule
import com.shifthackz.joyreactor.storage.di.storageModule
Expand All @@ -18,6 +19,7 @@ class JoyReactorApplication : Application() {
networkModule,
storageModule,
*dataModule,
domainModule,
viewModelModule,
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.shifthackz.joyreactor.domain.di

import com.shifthackz.joyreactor.domain.usecase.GetPostsPageUseCase
import com.shifthackz.joyreactor.domain.usecase.GetPostsPageUseCaseImpl
import org.koin.core.module.dsl.factoryOf
import org.koin.dsl.bind
import org.koin.dsl.module

val domainModule = module {
factoryOf(::GetPostsPageUseCaseImpl) bind GetPostsPageUseCase::class
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.shifthackz.joyreactor.domain.usecase

import com.shifthackz.joyreactor.entity.PagePayload
import com.shifthackz.joyreactor.entity.Post

interface GetPostsPageUseCase {
suspend operator fun invoke(url: String): PagePayload<Post>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.shifthackz.joyreactor.domain.usecase

import com.shifthackz.joyreactor.domain.repository.PostsRepository
import com.shifthackz.joyreactor.entity.PagePayload
import com.shifthackz.joyreactor.entity.Post

internal class GetPostsPageUseCaseImpl(
private val postsRepository: PostsRepository,
) : GetPostsPageUseCase {

override suspend fun invoke(url: String): PagePayload<Post> {
return postsRepository.fetchPage(url)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.shifthackz.joyreactor.entity

enum class JoyReactorLink(val url: String) {
NEW("https://joyreactor.cc/all"),
GOOD("https://joyreactor.cc"),
BEST("https://joyreactor.cc/best"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.shifthackz.joyreactor.entity.JoyReactorLink
import com.shifthackz.joyreactor.presentation.navigation.Argument
import com.shifthackz.joyreactor.presentation.navigation.Route
import com.shifthackz.joyreactor.presentation.ui.screen.test.PostsScreen
import com.shifthackz.joyreactor.presentation.ui.screen.test.PostsViewModel
import com.shifthackz.joyreactor.presentation.navigation.decodeNavArg
import com.shifthackz.joyreactor.presentation.navigation.encodeNavArg
import com.shifthackz.joyreactor.presentation.ui.screen.home.homeNavGraph
import com.shifthackz.joyreactor.presentation.ui.screen.posts.PostsScreen
import com.shifthackz.joyreactor.presentation.ui.theme.JoyYouTheme
import org.koin.androidx.compose.koinViewModel
import org.koin.androidx.viewmodel.ext.android.getViewModel
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import java.net.URLDecoder
import java.net.URLEncoder
import java.nio.charset.StandardCharsets

class MainActivity : ComponentActivity() {

Expand All @@ -31,44 +30,42 @@ class MainActivity : ComponentActivity() {
setContent {
val navController = rememberNavController()
val navigateBack: () -> Unit = { navController.navigateUp() }
val openPosts: (String) -> Unit = { url ->
navController.navigate(
Route.build(Route.POSTS, mapOf(Argument.URL to url.encodeNavArg()))
)
}
JoyYouTheme(dynamicColor = false) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
) {
NavHost(
navController = navController,
startDestination = Route.POSTS.value,
startDestination = Route.HOME.value,
) {
homeNavGraph(
route = Route.HOME,
openPosts = openPosts,
)

composable(
route = Route.POSTS.value,
arguments = listOf(
navArgument("url") { type = NavType.StringType }
navArgument(Argument.URL) { type = NavType.StringType }
),
) { entry ->
val url = entry.arguments
?.getString("url")
?.let {
URLDecoder.decode(it, StandardCharsets.UTF_8.toString())
}
?: "https://joyreactor.cc/all"
// ?: "https://PR.reactor.cc/"
// ?: "https://joyreactor.cc/tag/%D0%B4%D0%B5%D0%BD%D1%8C+%D1%80%D0%BE%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F+%D1%80%D0%B5%D0%B0%D0%BA%D1%82%D0%BE%D1%80%D1%87%D0%B0%D0%BD"
?.getString(Argument.URL)
?.let(String::decodeNavArg)
?: JoyReactorLink.NEW.url

val viewModel = koinViewModel<PostsViewModel>(
parameters = {
parametersOf(url)
}
)
PostsScreen(
viewModel = viewModel,
viewModel = koinViewModel(
parameters = { parametersOf(url) },
),
navigateBack = navigateBack,
openPosts = { rawUrl ->
val encodedUrl = URLEncoder.encode(rawUrl, StandardCharsets.UTF_8.toString())
navController.navigate(
Route.build(Route.POSTS, mapOf("url" to encodedUrl))
)
},
openPosts = openPosts,
).Build()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.shifthackz.joyreactor.presentation.di

import org.koin.dsl.module
import com.shifthackz.joyreactor.presentation.ui.screen.test.PostsViewModel
import com.shifthackz.joyreactor.presentation.ui.screen.posts.PostsViewModel
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module

val viewModelModule = module {
viewModel { parameters ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.shifthackz.joyreactor.presentation.navigation

object Argument {
const val URL = "url"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.shifthackz.joyreactor.presentation.navigation

import java.net.URLDecoder
import java.net.URLEncoder
import java.nio.charset.StandardCharsets

fun String.encodeNavArg(): String {
return URLEncoder.encode(this, StandardCharsets.UTF_8.toString())
}

fun String.decodeNavArg(): String {
return URLDecoder.decode(this, StandardCharsets.UTF_8.toString())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.shifthackz.joyreactor.presentation.navigation

import androidx.compose.runtime.Composable
import com.shifthackz.joyreactor.presentation.entity.TextUI

abstract class NavItem {
abstract val route: Route
abstract val name: TextUI
abstract val content: @Composable () -> Unit
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import android.util.Log

enum class Route(val value: String) {
POSTS("posts/{url}"),
TAG("tag");
HOME("home"),
HOME_FEED("home_feed"),
HOME_FEED_NEW("home_feed_new"),
HOME_FEED_GOOD("home_feed_good"),
HOME_FEED_BEST("home_feed_best");

companion object {
fun build(route: Route, params: Map<String, String> = mapOf()): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,28 @@ package com.shifthackz.joyreactor.presentation.ui.paging

import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.shifthackz.joyreactor.domain.usecase.GetPostsPageUseCase
import com.shifthackz.joyreactor.entity.JoyReactorLink
import com.shifthackz.joyreactor.entity.Post
import com.shifthackz.joyreactor.domain.repository.PostsRepository

class PostsPagingSource(
private val postsRepository: PostsRepository,
private val firstKey: String = FIRST_KEY,
private val getPostsPageUseCase: GetPostsPageUseCase,
private val firstKey: String = JoyReactorLink.NEW.url,
) : PagingSource<String, Post>() {

// override val keyReuseSupported: Boolean = true

override fun getRefreshKey(state: PagingState<String, Post>): String? {
override fun getRefreshKey(state: PagingState<String, Post>): String {
return firstKey
}

override suspend fun load(params: LoadParams<String>): LoadResult<String, Post> {
val pageNext = params.key ?: firstKey
return postsRepository
.fetchPage(pageNext)
.let {
LoadResult.Page(
data = it.data,
prevKey = it.prev,
nextKey = it.next
)
}
}

companion object {
const val FIRST_KEY = "https://joyreactor.cc/all"
return getPostsPageUseCase(params.key ?: firstKey).let { page ->
LoadResult.Page(
data = page.data,
prevKey = page.prev,
nextKey = page.next
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.shifthackz.joyreactor.presentation.ui.screen.feed

import androidx.compose.runtime.Composable
import com.shifthackz.joyreactor.entity.JoyReactorLink
import com.shifthackz.joyreactor.presentation.R
import com.shifthackz.joyreactor.presentation.entity.asTextUi
import com.shifthackz.joyreactor.presentation.navigation.Route
import com.shifthackz.joyreactor.presentation.ui.screen.posts.PostsScreen
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf

@Composable
fun newTab(
openPosts: (String) -> Unit,
) = FeedNavItem(
route = Route.HOME_FEED_NEW,
name = R.string.feed_tab_new.asTextUi(),
content = {
PostsScreen(
viewModel = koinViewModel(
parameters = { parametersOf(JoyReactorLink.NEW.url) }
),
openPosts = openPosts,
).Build()
}
)

@Composable
fun goodTab(
openPosts: (String) -> Unit,
) = FeedNavItem(
route = Route.HOME_FEED_GOOD,
name = R.string.feed_tab_good.asTextUi(),
content = {
PostsScreen(
viewModel = koinViewModel(
parameters = { parametersOf(JoyReactorLink.GOOD.url) }
),
openPosts = openPosts,
).Build()
}
)

@Composable
fun bestTab(
openPosts: (String) -> Unit,
) = FeedNavItem(
route = Route.HOME_FEED_BEST,
name = R.string.feed_tab_best.asTextUi(),
content = {
PostsScreen(
viewModel = koinViewModel(
parameters = { parametersOf(JoyReactorLink.BEST.url) }
),
openPosts = openPosts,
).Build()
}
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.shifthackz.joyreactor.presentation.ui.screen.feed

import androidx.compose.runtime.Composable
import com.shifthackz.joyreactor.presentation.entity.TextUI
import com.shifthackz.joyreactor.presentation.navigation.NavItem
import com.shifthackz.joyreactor.presentation.navigation.Route

data class FeedNavItem(
override val route: Route,
override val name: TextUI,
override val content: @Composable () -> Unit,
) : NavItem()
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.shifthackz.joyreactor.presentation.ui.screen.feed

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.shifthackz.joyreactor.presentation.ui.widget.ChipComposable
import com.shifthackz.joyreactor.presentation.ui.widget.NestedNavComposable

@Composable
fun FeedNavScreen(
modifier: Modifier = Modifier,
navItems: List<FeedNavItem>,
) {
NestedNavComposable(
modifier = modifier,
navItems = navItems,
topBar = { backStackEntry, navigate ->
val currentRoute = backStackEntry.value?.destination?.route
Row(
modifier = Modifier.padding(all = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
navItems.forEach { item ->
val selected = item.route.value == currentRoute
ChipComposable(
modifier = Modifier.clickable { navigate(item.route) },
selected = selected,
text = item.name,
)
}
}
}
)
}
Loading